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/README.md
CHANGED
|
@@ -283,7 +283,7 @@ const googleUserIds = await users.listIds({
|
|
|
283
283
|
partitionValues: { 'utm.source': 'google' }
|
|
284
284
|
});
|
|
285
285
|
|
|
286
|
-
// Paginar resultados
|
|
286
|
+
// Paginar resultados com contagem total
|
|
287
287
|
const page = await users.page({
|
|
288
288
|
partition: 'byUtmSource',
|
|
289
289
|
partitionValues: { 'utm.source': 'google' },
|
|
@@ -291,7 +291,20 @@ const page = await users.page({
|
|
|
291
291
|
size: 10
|
|
292
292
|
});
|
|
293
293
|
console.log(page.items); // Array de usuários
|
|
294
|
-
console.log(page.totalItems, page.totalPages);
|
|
294
|
+
console.log(page.totalItems, page.totalPages); // Contagem total e páginas
|
|
295
|
+
console.log(page.page, page.pageSize); // Página atual e tamanho
|
|
296
|
+
|
|
297
|
+
// Paginar resultados sem contagem total (mais rápido para grandes coleções)
|
|
298
|
+
const fastPage = await users.page({
|
|
299
|
+
partition: 'byUtmSource',
|
|
300
|
+
partitionValues: { 'utm.source': 'google' },
|
|
301
|
+
offset: 0,
|
|
302
|
+
size: 10,
|
|
303
|
+
skipCount: true // Pula a contagem total para melhor performance
|
|
304
|
+
});
|
|
305
|
+
console.log(fastPage.items); // Array de usuários
|
|
306
|
+
console.log(fastPage.totalItems); // null (não contado)
|
|
307
|
+
console.log(fastPage._debug); // Informações de debug
|
|
295
308
|
|
|
296
309
|
// Contar documentos em uma partição
|
|
297
310
|
const count = await users.count({
|
|
@@ -960,6 +973,73 @@ const count = await users.count();
|
|
|
960
973
|
console.log(`Total users: ${count}`);
|
|
961
974
|
```
|
|
962
975
|
|
|
976
|
+
#### Page Documents
|
|
977
|
+
|
|
978
|
+
The `page()` method provides efficient pagination with optional total count for performance optimization.
|
|
979
|
+
|
|
980
|
+
```javascript
|
|
981
|
+
// Basic pagination with total count
|
|
982
|
+
const page = await users.page({
|
|
983
|
+
offset: 0,
|
|
984
|
+
size: 10
|
|
985
|
+
});
|
|
986
|
+
|
|
987
|
+
console.log(page.items); // Array of user objects
|
|
988
|
+
console.log(page.totalItems); // Total number of items
|
|
989
|
+
console.log(page.totalPages); // Total number of pages
|
|
990
|
+
console.log(page.page); // Current page number (0-based)
|
|
991
|
+
console.log(page.pageSize); // Items per page
|
|
992
|
+
console.log(page._debug); // Debug information
|
|
993
|
+
|
|
994
|
+
// Pagination with partition filtering
|
|
995
|
+
const googleUsersPage = await users.page({
|
|
996
|
+
partition: 'byUtmSource',
|
|
997
|
+
partitionValues: { 'utm.source': 'google' },
|
|
998
|
+
offset: 0,
|
|
999
|
+
size: 5
|
|
1000
|
+
});
|
|
1001
|
+
|
|
1002
|
+
// Skip total count for better performance on large collections
|
|
1003
|
+
const fastPage = await users.page({
|
|
1004
|
+
offset: 0,
|
|
1005
|
+
size: 100,
|
|
1006
|
+
skipCount: true // Skips counting total items
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
console.log(fastPage.totalItems); // null (not counted)
|
|
1010
|
+
console.log(fastPage.totalPages); // null (not calculated)
|
|
1011
|
+
console.log(fastPage._debug.skipCount); // true
|
|
1012
|
+
```
|
|
1013
|
+
|
|
1014
|
+
**Page Response Structure:**
|
|
1015
|
+
|
|
1016
|
+
```javascript
|
|
1017
|
+
{
|
|
1018
|
+
items: Array, // Array of document objects
|
|
1019
|
+
totalItems: number, // Total count (null if skipCount: true)
|
|
1020
|
+
page: number, // Current page number (0-based)
|
|
1021
|
+
pageSize: number, // Number of items per page
|
|
1022
|
+
totalPages: number, // Total pages (null if skipCount: true)
|
|
1023
|
+
_debug: { // Debug information
|
|
1024
|
+
requestedSize: number,
|
|
1025
|
+
requestedOffset: number,
|
|
1026
|
+
actualItemsReturned: number,
|
|
1027
|
+
skipCount: boolean,
|
|
1028
|
+
hasTotalItems: boolean
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
```
|
|
1032
|
+
|
|
1033
|
+
**Parameters:**
|
|
1034
|
+
|
|
1035
|
+
| Parameter | Type | Default | Description |
|
|
1036
|
+
|-----------|------|---------|-------------|
|
|
1037
|
+
| `offset` | `number` | `0` | Number of items to skip |
|
|
1038
|
+
| `size` | `number` | `100` | Number of items per page |
|
|
1039
|
+
| `partition` | `string` | `null` | Partition name to filter by |
|
|
1040
|
+
| `partitionValues` | `object` | `{}` | Partition field values |
|
|
1041
|
+
| `skipCount` | `boolean` | `false` | Skip total count for performance |
|
|
1042
|
+
|
|
963
1043
|
### Bulk Operations
|
|
964
1044
|
|
|
965
1045
|
#### Insert Many
|
|
@@ -1680,7 +1760,7 @@ await products.insert({
|
|
|
1680
1760
|
| `listIds()` | Get array of document IDs | ✅ `{ partition, partitionValues }` |
|
|
1681
1761
|
| `count()` | Count documents | ✅ `{ partition, partitionValues }` |
|
|
1682
1762
|
| `listByPartition()` | List documents by partition | ✅ `{ partition, partitionValues }` |
|
|
1683
|
-
| `page()` | Paginate documents | ✅ `{ partition, partitionValues }` |
|
|
1763
|
+
| `page()` | Paginate documents | ✅ `{ partition, partitionValues, skipCount }` |
|
|
1684
1764
|
| `getFromPartition()` | Get single document from partition | ✅ Direct partition access |
|
|
1685
1765
|
| `query()` | Filter documents in memory | ❌ No partition support |
|
|
1686
1766
|
|
|
@@ -1767,19 +1847,6 @@ const s3db = new S3db({
|
|
|
1767
1847
|
- **`enforce-limits`**: Strict validation to prevent oversized documents
|
|
1768
1848
|
- **`user-management`**: Default behavior with warnings and monitoring
|
|
1769
1849
|
|
|
1770
|
-
### ✅ Recent Improvements
|
|
1771
|
-
|
|
1772
|
-
**🔧 Enhanced Data Serialization (v3.3.2+)**
|
|
1773
|
-
|
|
1774
|
-
s3db.js now handles complex data structures robustly:
|
|
1775
|
-
|
|
1776
|
-
- **Empty Arrays**: `[]` correctly serialized and preserved
|
|
1777
|
-
- **Null Arrays**: `null` values maintained without corruption
|
|
1778
|
-
- **Special Characters**: Arrays with pipe `|` characters properly escaped
|
|
1779
|
-
- **Empty Objects**: `{}` correctly mapped and stored
|
|
1780
|
-
- **Null Objects**: `null` object values preserved during serialization
|
|
1781
|
-
- **Nested Structures**: Complex nested objects with mixed empty/null values supported
|
|
1782
|
-
|
|
1783
1850
|
### Best Practices
|
|
1784
1851
|
|
|
1785
1852
|
#### 1. Design for Document Storage
|
package/dist/s3db.cjs.js
CHANGED
|
@@ -9236,36 +9236,97 @@ class Resource extends EventEmitter {
|
|
|
9236
9236
|
*/
|
|
9237
9237
|
async get(id) {
|
|
9238
9238
|
const key = this.getResourceKey(id);
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
|
|
9242
|
-
|
|
9243
|
-
|
|
9244
|
-
|
|
9245
|
-
|
|
9246
|
-
|
|
9247
|
-
|
|
9248
|
-
|
|
9249
|
-
|
|
9250
|
-
|
|
9239
|
+
try {
|
|
9240
|
+
const request = await this.client.headObject(key);
|
|
9241
|
+
const objectVersion = this.extractVersionFromKey(key) || this.version;
|
|
9242
|
+
const schema = await this.getSchemaForVersion(objectVersion);
|
|
9243
|
+
let metadata = await schema.unmapper(request.Metadata);
|
|
9244
|
+
const behaviorImpl = getBehavior(this.behavior);
|
|
9245
|
+
let body = "";
|
|
9246
|
+
if (request.ContentLength > 0) {
|
|
9247
|
+
try {
|
|
9248
|
+
const fullObject = await this.client.getObject(key);
|
|
9249
|
+
body = await streamToString(fullObject.Body);
|
|
9250
|
+
} catch (error) {
|
|
9251
|
+
console.warn(`Failed to read body for resource ${id}:`, error.message);
|
|
9252
|
+
body = "";
|
|
9253
|
+
}
|
|
9251
9254
|
}
|
|
9255
|
+
const { metadata: processedMetadata } = await behaviorImpl.handleGet({
|
|
9256
|
+
resource: this,
|
|
9257
|
+
metadata,
|
|
9258
|
+
body
|
|
9259
|
+
});
|
|
9260
|
+
let data = processedMetadata;
|
|
9261
|
+
data.id = id;
|
|
9262
|
+
data._contentLength = request.ContentLength;
|
|
9263
|
+
data._lastModified = request.LastModified;
|
|
9264
|
+
data._hasContent = request.ContentLength > 0;
|
|
9265
|
+
data._mimeType = request.ContentType || null;
|
|
9266
|
+
if (request.VersionId) data._versionId = request.VersionId;
|
|
9267
|
+
if (request.Expiration) data._expiresAt = request.Expiration;
|
|
9268
|
+
data._definitionHash = this.getDefinitionHash();
|
|
9269
|
+
this.emit("get", data);
|
|
9270
|
+
return data;
|
|
9271
|
+
} catch (error) {
|
|
9272
|
+
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError") || error.originalError?.message?.includes("Cipher job failed")) {
|
|
9273
|
+
try {
|
|
9274
|
+
console.warn(`Decryption failed for resource ${id}, attempting to get raw metadata`);
|
|
9275
|
+
const request = await this.client.headObject(key);
|
|
9276
|
+
const objectVersion = this.extractVersionFromKey(key) || this.version;
|
|
9277
|
+
const tempSchema = new Schema({
|
|
9278
|
+
name: this.name,
|
|
9279
|
+
attributes: this.attributes,
|
|
9280
|
+
passphrase: this.passphrase,
|
|
9281
|
+
version: objectVersion,
|
|
9282
|
+
options: {
|
|
9283
|
+
...this.options,
|
|
9284
|
+
autoDecrypt: false,
|
|
9285
|
+
// Disable decryption
|
|
9286
|
+
autoEncrypt: false
|
|
9287
|
+
// Disable encryption
|
|
9288
|
+
}
|
|
9289
|
+
});
|
|
9290
|
+
let metadata = await tempSchema.unmapper(request.Metadata);
|
|
9291
|
+
const behaviorImpl = getBehavior(this.behavior);
|
|
9292
|
+
let body = "";
|
|
9293
|
+
if (request.ContentLength > 0) {
|
|
9294
|
+
try {
|
|
9295
|
+
const fullObject = await this.client.getObject(key);
|
|
9296
|
+
body = await streamToString(fullObject.Body);
|
|
9297
|
+
} catch (bodyError) {
|
|
9298
|
+
console.warn(`Failed to read body for resource ${id}:`, bodyError.message);
|
|
9299
|
+
body = "";
|
|
9300
|
+
}
|
|
9301
|
+
}
|
|
9302
|
+
const { metadata: processedMetadata } = await behaviorImpl.handleGet({
|
|
9303
|
+
resource: this,
|
|
9304
|
+
metadata,
|
|
9305
|
+
body
|
|
9306
|
+
});
|
|
9307
|
+
let data = processedMetadata;
|
|
9308
|
+
data.id = id;
|
|
9309
|
+
data._contentLength = request.ContentLength;
|
|
9310
|
+
data._lastModified = request.LastModified;
|
|
9311
|
+
data._hasContent = request.ContentLength > 0;
|
|
9312
|
+
data._mimeType = request.ContentType || null;
|
|
9313
|
+
data._version = objectVersion;
|
|
9314
|
+
data._decryptionFailed = true;
|
|
9315
|
+
if (request.VersionId) data._versionId = request.VersionId;
|
|
9316
|
+
if (request.Expiration) data._expiresAt = request.Expiration;
|
|
9317
|
+
data._definitionHash = this.getDefinitionHash();
|
|
9318
|
+
this.emit("get", data);
|
|
9319
|
+
return data;
|
|
9320
|
+
} catch (fallbackError) {
|
|
9321
|
+
console.error(`Fallback attempt also failed for resource ${id}:`, fallbackError.message);
|
|
9322
|
+
}
|
|
9323
|
+
}
|
|
9324
|
+
const enhancedError = new Error(`Failed to get resource with id '${id}': ${error.message}`);
|
|
9325
|
+
enhancedError.originalError = error;
|
|
9326
|
+
enhancedError.resourceId = id;
|
|
9327
|
+
enhancedError.resourceKey = key;
|
|
9328
|
+
throw enhancedError;
|
|
9252
9329
|
}
|
|
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
9330
|
}
|
|
9270
9331
|
/**
|
|
9271
9332
|
* Check if a resource exists by ID
|
|
@@ -9547,15 +9608,21 @@ class Resource extends EventEmitter {
|
|
|
9547
9608
|
return { deletedCount, resource: this.name };
|
|
9548
9609
|
}
|
|
9549
9610
|
/**
|
|
9550
|
-
* List resource IDs with optional partition filtering
|
|
9611
|
+
* List resource IDs with optional partition filtering and pagination
|
|
9551
9612
|
* @param {Object} [params] - List parameters
|
|
9552
9613
|
* @param {string} [params.partition] - Partition name to list from
|
|
9553
9614
|
* @param {Object} [params.partitionValues] - Partition field values to filter by
|
|
9615
|
+
* @param {number} [params.limit] - Maximum number of results to return
|
|
9616
|
+
* @param {number} [params.offset=0] - Offset for pagination
|
|
9554
9617
|
* @returns {Promise<string[]>} Array of resource IDs (strings)
|
|
9555
9618
|
* @example
|
|
9556
9619
|
* // List all IDs
|
|
9557
9620
|
* const allIds = await resource.listIds();
|
|
9558
9621
|
*
|
|
9622
|
+
* // List IDs with pagination
|
|
9623
|
+
* const firstPageIds = await resource.listIds({ limit: 10, offset: 0 });
|
|
9624
|
+
* const secondPageIds = await resource.listIds({ limit: 10, offset: 10 });
|
|
9625
|
+
*
|
|
9559
9626
|
* // List IDs from specific partition
|
|
9560
9627
|
* const googleUserIds = await resource.listIds({
|
|
9561
9628
|
* partition: 'byUtmSource',
|
|
@@ -9568,7 +9635,7 @@ class Resource extends EventEmitter {
|
|
|
9568
9635
|
* partitionValues: { category: 'electronics', region: 'US' }
|
|
9569
9636
|
* });
|
|
9570
9637
|
*/
|
|
9571
|
-
async listIds({ partition = null, partitionValues = {} } = {}) {
|
|
9638
|
+
async listIds({ partition = null, partitionValues = {}, limit, offset = 0 } = {}) {
|
|
9572
9639
|
let prefix;
|
|
9573
9640
|
if (partition && Object.keys(partitionValues).length > 0) {
|
|
9574
9641
|
const partitionDef = this.options.partitions[partition];
|
|
@@ -9592,8 +9659,11 @@ class Resource extends EventEmitter {
|
|
|
9592
9659
|
} else {
|
|
9593
9660
|
prefix = `resource=${this.name}/v=${this.version}`;
|
|
9594
9661
|
}
|
|
9595
|
-
const keys = await this.client.
|
|
9596
|
-
prefix
|
|
9662
|
+
const keys = await this.client.getKeysPage({
|
|
9663
|
+
prefix,
|
|
9664
|
+
offset,
|
|
9665
|
+
amount: limit || 1e3
|
|
9666
|
+
// Default to 1000 if no limit specified
|
|
9597
9667
|
});
|
|
9598
9668
|
const ids = keys.map((key) => {
|
|
9599
9669
|
const parts = key.split("/");
|
|
@@ -9640,11 +9710,27 @@ class Resource extends EventEmitter {
|
|
|
9640
9710
|
if (limit) {
|
|
9641
9711
|
filteredIds2 = filteredIds2.slice(0, limit);
|
|
9642
9712
|
}
|
|
9643
|
-
const { results: results2 } = await promisePool.PromisePool.for(filteredIds2).withConcurrency(this.parallelism).
|
|
9644
|
-
|
|
9713
|
+
const { results: results2, errors: errors2 } = await promisePool.PromisePool.for(filteredIds2).withConcurrency(this.parallelism).handleError(async (error, id) => {
|
|
9714
|
+
console.warn(`Failed to get resource ${id}:`, error.message);
|
|
9715
|
+
return null;
|
|
9716
|
+
}).process(async (id) => {
|
|
9717
|
+
try {
|
|
9718
|
+
return await this.get(id);
|
|
9719
|
+
} catch (error) {
|
|
9720
|
+
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9721
|
+
console.warn(`Decryption failed for ${id}, returning basic info`);
|
|
9722
|
+
return {
|
|
9723
|
+
id,
|
|
9724
|
+
_decryptionFailed: true,
|
|
9725
|
+
_error: error.message
|
|
9726
|
+
};
|
|
9727
|
+
}
|
|
9728
|
+
throw error;
|
|
9729
|
+
}
|
|
9645
9730
|
});
|
|
9646
|
-
|
|
9647
|
-
|
|
9731
|
+
const validResults2 = results2.filter((item) => item !== null);
|
|
9732
|
+
this.emit("list", { partition, partitionValues, count: validResults2.length, errors: errors2.length });
|
|
9733
|
+
return validResults2;
|
|
9648
9734
|
}
|
|
9649
9735
|
const partitionDef = this.options.partitions[partition];
|
|
9650
9736
|
if (!partitionDef) {
|
|
@@ -9675,11 +9761,29 @@ class Resource extends EventEmitter {
|
|
|
9675
9761
|
if (limit) {
|
|
9676
9762
|
filteredIds = filteredIds.slice(0, limit);
|
|
9677
9763
|
}
|
|
9678
|
-
const { results } = await promisePool.PromisePool.for(filteredIds).withConcurrency(this.parallelism).
|
|
9679
|
-
|
|
9764
|
+
const { results, errors } = await promisePool.PromisePool.for(filteredIds).withConcurrency(this.parallelism).handleError(async (error, id) => {
|
|
9765
|
+
console.warn(`Failed to get partition resource ${id}:`, error.message);
|
|
9766
|
+
return null;
|
|
9767
|
+
}).process(async (id) => {
|
|
9768
|
+
try {
|
|
9769
|
+
return await this.getFromPartition({ id, partitionName: partition, partitionValues });
|
|
9770
|
+
} catch (error) {
|
|
9771
|
+
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9772
|
+
console.warn(`Decryption failed for partition resource ${id}, returning basic info`);
|
|
9773
|
+
return {
|
|
9774
|
+
id,
|
|
9775
|
+
_partition: partition,
|
|
9776
|
+
_partitionValues: partitionValues,
|
|
9777
|
+
_decryptionFailed: true,
|
|
9778
|
+
_error: error.message
|
|
9779
|
+
};
|
|
9780
|
+
}
|
|
9781
|
+
throw error;
|
|
9782
|
+
}
|
|
9680
9783
|
});
|
|
9681
|
-
|
|
9682
|
-
|
|
9784
|
+
const validResults = results.filter((item) => item !== null);
|
|
9785
|
+
this.emit("list", { partition, partitionValues, count: validResults.length, errors: errors.length });
|
|
9786
|
+
return validResults;
|
|
9683
9787
|
}
|
|
9684
9788
|
/**
|
|
9685
9789
|
* Get multiple resources by their IDs
|
|
@@ -9690,11 +9794,30 @@ class Resource extends EventEmitter {
|
|
|
9690
9794
|
* users.forEach(user => console.log(user.name));
|
|
9691
9795
|
*/
|
|
9692
9796
|
async getMany(ids) {
|
|
9693
|
-
const { results } = await promisePool.PromisePool.for(ids).withConcurrency(this.client.parallelism).
|
|
9797
|
+
const { results, errors } = await promisePool.PromisePool.for(ids).withConcurrency(this.client.parallelism).handleError(async (error, id) => {
|
|
9798
|
+
console.warn(`Failed to get resource ${id}:`, error.message);
|
|
9799
|
+
return {
|
|
9800
|
+
id,
|
|
9801
|
+
_error: error.message,
|
|
9802
|
+
_decryptionFailed: error.message.includes("Cipher job failed") || error.message.includes("OperationError")
|
|
9803
|
+
};
|
|
9804
|
+
}).process(async (id) => {
|
|
9694
9805
|
this.emit("id", id);
|
|
9695
|
-
|
|
9696
|
-
|
|
9697
|
-
|
|
9806
|
+
try {
|
|
9807
|
+
const data = await this.get(id);
|
|
9808
|
+
this.emit("data", data);
|
|
9809
|
+
return data;
|
|
9810
|
+
} catch (error) {
|
|
9811
|
+
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9812
|
+
console.warn(`Decryption failed for ${id}, returning basic info`);
|
|
9813
|
+
return {
|
|
9814
|
+
id,
|
|
9815
|
+
_decryptionFailed: true,
|
|
9816
|
+
_error: error.message
|
|
9817
|
+
};
|
|
9818
|
+
}
|
|
9819
|
+
throw error;
|
|
9820
|
+
}
|
|
9698
9821
|
});
|
|
9699
9822
|
this.emit("getMany", ids.length);
|
|
9700
9823
|
return results;
|
|
@@ -9709,9 +9832,28 @@ class Resource extends EventEmitter {
|
|
|
9709
9832
|
async getAll() {
|
|
9710
9833
|
let ids = await this.listIds();
|
|
9711
9834
|
if (ids.length === 0) return [];
|
|
9712
|
-
const { results } = await promisePool.PromisePool.for(ids).withConcurrency(this.client.parallelism).
|
|
9713
|
-
|
|
9714
|
-
return
|
|
9835
|
+
const { results, errors } = await promisePool.PromisePool.for(ids).withConcurrency(this.client.parallelism).handleError(async (error, id) => {
|
|
9836
|
+
console.warn(`Failed to get resource ${id}:`, error.message);
|
|
9837
|
+
return {
|
|
9838
|
+
id,
|
|
9839
|
+
_error: error.message,
|
|
9840
|
+
_decryptionFailed: error.message.includes("Cipher job failed") || error.message.includes("OperationError")
|
|
9841
|
+
};
|
|
9842
|
+
}).process(async (id) => {
|
|
9843
|
+
try {
|
|
9844
|
+
const data = await this.get(id);
|
|
9845
|
+
return data;
|
|
9846
|
+
} catch (error) {
|
|
9847
|
+
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9848
|
+
console.warn(`Decryption failed for ${id}, returning basic info`);
|
|
9849
|
+
return {
|
|
9850
|
+
id,
|
|
9851
|
+
_decryptionFailed: true,
|
|
9852
|
+
_error: error.message
|
|
9853
|
+
};
|
|
9854
|
+
}
|
|
9855
|
+
throw error;
|
|
9856
|
+
}
|
|
9715
9857
|
});
|
|
9716
9858
|
this.emit("getAll", results.length);
|
|
9717
9859
|
return results;
|
|
@@ -9723,6 +9865,7 @@ class Resource extends EventEmitter {
|
|
|
9723
9865
|
* @param {number} [params.size=100] - Page size
|
|
9724
9866
|
* @param {string} [params.partition] - Partition name to page from
|
|
9725
9867
|
* @param {Object} [params.partitionValues] - Partition field values to filter by
|
|
9868
|
+
* @param {boolean} [params.skipCount=false] - Skip total count for performance (useful for large collections)
|
|
9726
9869
|
* @returns {Promise<Object>} Page result with items and pagination info
|
|
9727
9870
|
* @example
|
|
9728
9871
|
* // Get first page of all resources
|
|
@@ -9737,22 +9880,43 @@ class Resource extends EventEmitter {
|
|
|
9737
9880
|
* offset: 0,
|
|
9738
9881
|
* size: 5
|
|
9739
9882
|
* });
|
|
9883
|
+
*
|
|
9884
|
+
* // Skip count for performance in large collections
|
|
9885
|
+
* const fastPage = await resource.page({
|
|
9886
|
+
* offset: 0,
|
|
9887
|
+
* size: 100,
|
|
9888
|
+
* skipCount: true
|
|
9889
|
+
* });
|
|
9890
|
+
* console.log(`Got ${fastPage.items.length} items`); // totalItems will be null
|
|
9740
9891
|
*/
|
|
9741
|
-
async page({ offset = 0, size = 100, partition = null, partitionValues = {} } = {}) {
|
|
9742
|
-
|
|
9743
|
-
|
|
9744
|
-
|
|
9892
|
+
async page({ offset = 0, size = 100, partition = null, partitionValues = {}, skipCount = false } = {}) {
|
|
9893
|
+
let totalItems = null;
|
|
9894
|
+
let totalPages = null;
|
|
9895
|
+
if (!skipCount) {
|
|
9896
|
+
totalItems = await this.count({ partition, partitionValues });
|
|
9897
|
+
totalPages = Math.ceil(totalItems / size);
|
|
9898
|
+
}
|
|
9745
9899
|
const page = Math.floor(offset / size);
|
|
9746
|
-
const
|
|
9747
|
-
|
|
9748
|
-
|
|
9749
|
-
|
|
9900
|
+
const items = await this.list({
|
|
9901
|
+
partition,
|
|
9902
|
+
partitionValues,
|
|
9903
|
+
limit: size,
|
|
9904
|
+
offset
|
|
9905
|
+
});
|
|
9750
9906
|
const result = {
|
|
9751
9907
|
items,
|
|
9752
9908
|
totalItems,
|
|
9753
9909
|
page,
|
|
9754
9910
|
pageSize: size,
|
|
9755
|
-
totalPages
|
|
9911
|
+
totalPages,
|
|
9912
|
+
// Add additional metadata for debugging
|
|
9913
|
+
_debug: {
|
|
9914
|
+
requestedSize: size,
|
|
9915
|
+
requestedOffset: offset,
|
|
9916
|
+
actualItemsReturned: items.length,
|
|
9917
|
+
skipCount,
|
|
9918
|
+
hasTotalItems: totalItems !== null
|
|
9919
|
+
}
|
|
9756
9920
|
};
|
|
9757
9921
|
this.emit("page", result);
|
|
9758
9922
|
return result;
|
|
@@ -9904,7 +10068,27 @@ class Resource extends EventEmitter {
|
|
|
9904
10068
|
* @returns {Object} Schema object for the version
|
|
9905
10069
|
*/
|
|
9906
10070
|
async getSchemaForVersion(version) {
|
|
9907
|
-
|
|
10071
|
+
if (version === this.version) {
|
|
10072
|
+
return this.schema;
|
|
10073
|
+
}
|
|
10074
|
+
try {
|
|
10075
|
+
const compatibleSchema = new Schema({
|
|
10076
|
+
name: this.name,
|
|
10077
|
+
attributes: this.attributes,
|
|
10078
|
+
passphrase: this.passphrase,
|
|
10079
|
+
version,
|
|
10080
|
+
options: {
|
|
10081
|
+
...this.options,
|
|
10082
|
+
// For older versions, be more lenient with decryption
|
|
10083
|
+
autoDecrypt: true,
|
|
10084
|
+
autoEncrypt: true
|
|
10085
|
+
}
|
|
10086
|
+
});
|
|
10087
|
+
return compatibleSchema;
|
|
10088
|
+
} catch (error) {
|
|
10089
|
+
console.warn(`Failed to create compatible schema for version ${version}, using current schema:`, error.message);
|
|
10090
|
+
return this.schema;
|
|
10091
|
+
}
|
|
9908
10092
|
}
|
|
9909
10093
|
/**
|
|
9910
10094
|
* Create partition references after insert
|
|
@@ -10148,7 +10332,7 @@ class Database extends EventEmitter {
|
|
|
10148
10332
|
this.version = "1";
|
|
10149
10333
|
this.s3dbVersion = (() => {
|
|
10150
10334
|
try {
|
|
10151
|
-
return true ? "4.1.
|
|
10335
|
+
return true ? "4.1.9" : "latest";
|
|
10152
10336
|
} catch (e) {
|
|
10153
10337
|
return "latest";
|
|
10154
10338
|
}
|