s3db.js 4.1.6 → 4.1.8
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 +513 -146
- package/dist/s3db.cjs.min.js +9 -9
- package/dist/s3db.es.js +513 -146
- package/dist/s3db.es.min.js +9 -9
- package/dist/s3db.iife.js +513 -146
- package/dist/s3db.iife.min.js +10 -10
- package/package.json +1 -1
package/dist/s3db.iife.js
CHANGED
|
@@ -8560,6 +8560,14 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
8560
8560
|
}
|
|
8561
8561
|
return bytes;
|
|
8562
8562
|
}
|
|
8563
|
+
function calculateAttributeNamesSize(mappedObject) {
|
|
8564
|
+
let totalSize = 0;
|
|
8565
|
+
for (const key of Object.keys(mappedObject)) {
|
|
8566
|
+
if (key === "_v") continue;
|
|
8567
|
+
totalSize += calculateUTF8Bytes(key);
|
|
8568
|
+
}
|
|
8569
|
+
return totalSize;
|
|
8570
|
+
}
|
|
8563
8571
|
function transformValue(value) {
|
|
8564
8572
|
if (value === null || value === void 0) {
|
|
8565
8573
|
return "";
|
|
@@ -8594,49 +8602,31 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
8594
8602
|
return sizes;
|
|
8595
8603
|
}
|
|
8596
8604
|
function calculateTotalSize(mappedObject) {
|
|
8597
|
-
const
|
|
8598
|
-
|
|
8605
|
+
const valueSizes = calculateAttributeSizes(mappedObject);
|
|
8606
|
+
const valueTotal = Object.values(valueSizes).reduce((total, size) => total + size, 0);
|
|
8607
|
+
const namesSize = calculateAttributeNamesSize(mappedObject);
|
|
8608
|
+
return valueTotal + namesSize;
|
|
8599
8609
|
}
|
|
8600
8610
|
|
|
8601
|
-
const S3_METADATA_LIMIT_BYTES
|
|
8611
|
+
const S3_METADATA_LIMIT_BYTES = 2048;
|
|
8602
8612
|
async function handleInsert$3({ resource, data, mappedData }) {
|
|
8603
8613
|
const totalSize = calculateTotalSize(mappedData);
|
|
8604
|
-
if (totalSize > S3_METADATA_LIMIT_BYTES
|
|
8605
|
-
|
|
8606
|
-
operation: "insert",
|
|
8607
|
-
totalSize,
|
|
8608
|
-
limit: S3_METADATA_LIMIT_BYTES$3,
|
|
8609
|
-
excess: totalSize - S3_METADATA_LIMIT_BYTES$3,
|
|
8610
|
-
data
|
|
8611
|
-
});
|
|
8614
|
+
if (totalSize > S3_METADATA_LIMIT_BYTES) {
|
|
8615
|
+
throw new Error(`S3 metadata size exceeds 2KB limit. Current size: ${totalSize} bytes, limit: ${S3_METADATA_LIMIT_BYTES} bytes`);
|
|
8612
8616
|
}
|
|
8613
8617
|
return { mappedData, body: "" };
|
|
8614
8618
|
}
|
|
8615
8619
|
async function handleUpdate$3({ resource, id, data, mappedData }) {
|
|
8616
8620
|
const totalSize = calculateTotalSize(mappedData);
|
|
8617
|
-
if (totalSize > S3_METADATA_LIMIT_BYTES
|
|
8618
|
-
|
|
8619
|
-
operation: "update",
|
|
8620
|
-
id,
|
|
8621
|
-
totalSize,
|
|
8622
|
-
limit: S3_METADATA_LIMIT_BYTES$3,
|
|
8623
|
-
excess: totalSize - S3_METADATA_LIMIT_BYTES$3,
|
|
8624
|
-
data
|
|
8625
|
-
});
|
|
8621
|
+
if (totalSize > S3_METADATA_LIMIT_BYTES) {
|
|
8622
|
+
throw new Error(`S3 metadata size exceeds 2KB limit. Current size: ${totalSize} bytes, limit: ${S3_METADATA_LIMIT_BYTES} bytes`);
|
|
8626
8623
|
}
|
|
8627
8624
|
return { mappedData, body: "" };
|
|
8628
8625
|
}
|
|
8629
8626
|
async function handleUpsert$3({ resource, id, data, mappedData }) {
|
|
8630
8627
|
const totalSize = calculateTotalSize(mappedData);
|
|
8631
|
-
if (totalSize > S3_METADATA_LIMIT_BYTES
|
|
8632
|
-
|
|
8633
|
-
operation: "upsert",
|
|
8634
|
-
id,
|
|
8635
|
-
totalSize,
|
|
8636
|
-
limit: S3_METADATA_LIMIT_BYTES$3,
|
|
8637
|
-
excess: totalSize - S3_METADATA_LIMIT_BYTES$3,
|
|
8638
|
-
data
|
|
8639
|
-
});
|
|
8628
|
+
if (totalSize > S3_METADATA_LIMIT_BYTES) {
|
|
8629
|
+
throw new Error(`S3 metadata size exceeds 2KB limit. Current size: ${totalSize} bytes, limit: ${S3_METADATA_LIMIT_BYTES} bytes`);
|
|
8640
8630
|
}
|
|
8641
8631
|
return { mappedData, body: "" };
|
|
8642
8632
|
}
|
|
@@ -8644,33 +8634,53 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
8644
8634
|
return { metadata, body };
|
|
8645
8635
|
}
|
|
8646
8636
|
|
|
8647
|
-
var
|
|
8637
|
+
var enforceLimits = /*#__PURE__*/Object.freeze({
|
|
8648
8638
|
__proto__: null,
|
|
8639
|
+
S3_METADATA_LIMIT_BYTES: S3_METADATA_LIMIT_BYTES,
|
|
8649
8640
|
handleGet: handleGet$3,
|
|
8650
8641
|
handleInsert: handleInsert$3,
|
|
8651
8642
|
handleUpdate: handleUpdate$3,
|
|
8652
8643
|
handleUpsert: handleUpsert$3
|
|
8653
8644
|
});
|
|
8654
8645
|
|
|
8655
|
-
const S3_METADATA_LIMIT_BYTES$2 = 2e3;
|
|
8656
8646
|
async function handleInsert$2({ resource, data, mappedData }) {
|
|
8657
8647
|
const totalSize = calculateTotalSize(mappedData);
|
|
8658
|
-
if (totalSize > S3_METADATA_LIMIT_BYTES
|
|
8659
|
-
|
|
8648
|
+
if (totalSize > S3_METADATA_LIMIT_BYTES) {
|
|
8649
|
+
resource.emit("exceedsLimit", {
|
|
8650
|
+
operation: "insert",
|
|
8651
|
+
totalSize,
|
|
8652
|
+
limit: S3_METADATA_LIMIT_BYTES,
|
|
8653
|
+
excess: totalSize - S3_METADATA_LIMIT_BYTES,
|
|
8654
|
+
data
|
|
8655
|
+
});
|
|
8660
8656
|
}
|
|
8661
8657
|
return { mappedData, body: "" };
|
|
8662
8658
|
}
|
|
8663
8659
|
async function handleUpdate$2({ resource, id, data, mappedData }) {
|
|
8664
8660
|
const totalSize = calculateTotalSize(mappedData);
|
|
8665
|
-
if (totalSize > S3_METADATA_LIMIT_BYTES
|
|
8666
|
-
|
|
8661
|
+
if (totalSize > S3_METADATA_LIMIT_BYTES) {
|
|
8662
|
+
resource.emit("exceedsLimit", {
|
|
8663
|
+
operation: "update",
|
|
8664
|
+
id,
|
|
8665
|
+
totalSize,
|
|
8666
|
+
limit: S3_METADATA_LIMIT_BYTES,
|
|
8667
|
+
excess: totalSize - S3_METADATA_LIMIT_BYTES,
|
|
8668
|
+
data
|
|
8669
|
+
});
|
|
8667
8670
|
}
|
|
8668
8671
|
return { mappedData, body: "" };
|
|
8669
8672
|
}
|
|
8670
8673
|
async function handleUpsert$2({ resource, id, data, mappedData }) {
|
|
8671
8674
|
const totalSize = calculateTotalSize(mappedData);
|
|
8672
|
-
if (totalSize > S3_METADATA_LIMIT_BYTES
|
|
8673
|
-
|
|
8675
|
+
if (totalSize > S3_METADATA_LIMIT_BYTES) {
|
|
8676
|
+
resource.emit("exceedsLimit", {
|
|
8677
|
+
operation: "upsert",
|
|
8678
|
+
id,
|
|
8679
|
+
totalSize,
|
|
8680
|
+
limit: S3_METADATA_LIMIT_BYTES,
|
|
8681
|
+
excess: totalSize - S3_METADATA_LIMIT_BYTES,
|
|
8682
|
+
data
|
|
8683
|
+
});
|
|
8674
8684
|
}
|
|
8675
8685
|
return { mappedData, body: "" };
|
|
8676
8686
|
}
|
|
@@ -8678,7 +8688,7 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
8678
8688
|
return { metadata, body };
|
|
8679
8689
|
}
|
|
8680
8690
|
|
|
8681
|
-
var
|
|
8691
|
+
var userManagement = /*#__PURE__*/Object.freeze({
|
|
8682
8692
|
__proto__: null,
|
|
8683
8693
|
handleGet: handleGet$2,
|
|
8684
8694
|
handleInsert: handleInsert$2,
|
|
@@ -8686,7 +8696,6 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
8686
8696
|
handleUpsert: handleUpsert$2
|
|
8687
8697
|
});
|
|
8688
8698
|
|
|
8689
|
-
const S3_METADATA_LIMIT_BYTES$1 = 2e3;
|
|
8690
8699
|
const TRUNCATE_SUFFIX = "...";
|
|
8691
8700
|
const TRUNCATE_SUFFIX_BYTES = calculateUTF8Bytes(TRUNCATE_SUFFIX);
|
|
8692
8701
|
async function handleInsert$1({ resource, data, mappedData }) {
|
|
@@ -8707,7 +8716,7 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
8707
8716
|
const result = {};
|
|
8708
8717
|
let currentSize = 0;
|
|
8709
8718
|
for (const [key, size] of sortedAttributes) {
|
|
8710
|
-
const availableSpace = S3_METADATA_LIMIT_BYTES
|
|
8719
|
+
const availableSpace = S3_METADATA_LIMIT_BYTES - currentSize;
|
|
8711
8720
|
if (size <= availableSpace) {
|
|
8712
8721
|
result[key] = mappedData[key];
|
|
8713
8722
|
currentSize += size;
|
|
@@ -8727,7 +8736,7 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
8727
8736
|
}
|
|
8728
8737
|
}
|
|
8729
8738
|
result[key] = truncatedValue + TRUNCATE_SUFFIX;
|
|
8730
|
-
currentSize = S3_METADATA_LIMIT_BYTES
|
|
8739
|
+
currentSize = S3_METADATA_LIMIT_BYTES;
|
|
8731
8740
|
break;
|
|
8732
8741
|
} else {
|
|
8733
8742
|
break;
|
|
@@ -8744,7 +8753,6 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
8744
8753
|
handleUpsert: handleUpsert$1
|
|
8745
8754
|
});
|
|
8746
8755
|
|
|
8747
|
-
const S3_METADATA_LIMIT_BYTES = 2e3;
|
|
8748
8756
|
const OVERFLOW_FLAG = "$overflow";
|
|
8749
8757
|
const OVERFLOW_FLAG_VALUE = "true";
|
|
8750
8758
|
const OVERFLOW_FLAG_BYTES = calculateUTF8Bytes(OVERFLOW_FLAG) + calculateUTF8Bytes(OVERFLOW_FLAG_VALUE);
|
|
@@ -9087,13 +9095,29 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9087
9095
|
return join(`resource=${this.name}`, `v=${this.version}`, `id=${id}`);
|
|
9088
9096
|
}
|
|
9089
9097
|
/**
|
|
9090
|
-
*
|
|
9091
|
-
* @param {
|
|
9092
|
-
* @param {string}
|
|
9093
|
-
* @param {
|
|
9094
|
-
* @
|
|
9098
|
+
* Generate partition key for a resource in a specific partition
|
|
9099
|
+
* @param {Object} params - Partition key parameters
|
|
9100
|
+
* @param {string} params.partitionName - Name of the partition
|
|
9101
|
+
* @param {string} params.id - Resource ID
|
|
9102
|
+
* @param {Object} params.data - Resource data for partition value extraction
|
|
9103
|
+
* @returns {string|null} The partition key path or null if required fields are missing
|
|
9104
|
+
* @example
|
|
9105
|
+
* const partitionKey = resource.getPartitionKey({
|
|
9106
|
+
* partitionName: 'byUtmSource',
|
|
9107
|
+
* id: 'user-123',
|
|
9108
|
+
* data: { utm: { source: 'google' } }
|
|
9109
|
+
* });
|
|
9110
|
+
* // Returns: 'resource=users/partition=byUtmSource/utm.source=google/id=user-123'
|
|
9111
|
+
*
|
|
9112
|
+
* // Returns null if required field is missing
|
|
9113
|
+
* const nullKey = resource.getPartitionKey({
|
|
9114
|
+
* partitionName: 'byUtmSource',
|
|
9115
|
+
* id: 'user-123',
|
|
9116
|
+
* data: { name: 'John' } // Missing utm.source
|
|
9117
|
+
* });
|
|
9118
|
+
* // Returns: null
|
|
9095
9119
|
*/
|
|
9096
|
-
getPartitionKey(partitionName, id, data) {
|
|
9120
|
+
getPartitionKey({ partitionName, id, data }) {
|
|
9097
9121
|
const partition = this.options.partitions[partitionName];
|
|
9098
9122
|
if (!partition) {
|
|
9099
9123
|
throw new Error(`Partition '${partitionName}' not found`);
|
|
@@ -9124,15 +9148,36 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9124
9148
|
return data[fieldPath];
|
|
9125
9149
|
}
|
|
9126
9150
|
const keys = fieldPath.split(".");
|
|
9127
|
-
let
|
|
9151
|
+
let currentLevel = data;
|
|
9128
9152
|
for (const key of keys) {
|
|
9129
|
-
if (
|
|
9153
|
+
if (!currentLevel || typeof currentLevel !== "object" || !(key in currentLevel)) {
|
|
9130
9154
|
return void 0;
|
|
9131
9155
|
}
|
|
9132
|
-
|
|
9156
|
+
currentLevel = currentLevel[key];
|
|
9133
9157
|
}
|
|
9134
|
-
return
|
|
9158
|
+
return currentLevel;
|
|
9135
9159
|
}
|
|
9160
|
+
/**
|
|
9161
|
+
* Insert a new resource object
|
|
9162
|
+
* @param {Object} params - Insert parameters
|
|
9163
|
+
* @param {string} [params.id] - Resource ID (auto-generated if not provided)
|
|
9164
|
+
* @param {...Object} params - Resource attributes (any additional properties)
|
|
9165
|
+
* @returns {Promise<Object>} The inserted resource object with all attributes and generated ID
|
|
9166
|
+
* @example
|
|
9167
|
+
* // Insert with auto-generated ID
|
|
9168
|
+
* const user = await resource.insert({
|
|
9169
|
+
* name: 'John Doe',
|
|
9170
|
+
* email: 'john@example.com',
|
|
9171
|
+
* age: 30
|
|
9172
|
+
* });
|
|
9173
|
+
*
|
|
9174
|
+
* // Insert with custom ID
|
|
9175
|
+
* const user = await resource.insert({
|
|
9176
|
+
* id: 'custom-id-123',
|
|
9177
|
+
* name: 'Jane Smith',
|
|
9178
|
+
* email: 'jane@example.com'
|
|
9179
|
+
* });
|
|
9180
|
+
*/
|
|
9136
9181
|
async insert({ id, ...attributes }) {
|
|
9137
9182
|
if (this.options.timestamps) {
|
|
9138
9183
|
attributes.createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -9171,39 +9216,68 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9171
9216
|
this.emit("insert", final);
|
|
9172
9217
|
return final;
|
|
9173
9218
|
}
|
|
9219
|
+
/**
|
|
9220
|
+
* Retrieve a resource object by ID
|
|
9221
|
+
* @param {string} id - Resource ID
|
|
9222
|
+
* @returns {Promise<Object>} The resource object with all attributes and metadata
|
|
9223
|
+
* @example
|
|
9224
|
+
* const user = await resource.get('user-123');
|
|
9225
|
+
* console.log(user.name); // 'John Doe'
|
|
9226
|
+
* console.log(user._lastModified); // Date object
|
|
9227
|
+
* console.log(user._hasContent); // boolean
|
|
9228
|
+
*/
|
|
9174
9229
|
async get(id) {
|
|
9175
9230
|
const key = this.getResourceKey(id);
|
|
9176
|
-
|
|
9177
|
-
|
|
9178
|
-
|
|
9179
|
-
|
|
9180
|
-
|
|
9181
|
-
|
|
9182
|
-
|
|
9183
|
-
|
|
9184
|
-
|
|
9185
|
-
|
|
9186
|
-
|
|
9187
|
-
|
|
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
|
+
}
|
|
9188
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
|
+
const enhancedError = new Error(`Failed to get resource with id '${id}': ${error.message}`);
|
|
9265
|
+
enhancedError.originalError = error;
|
|
9266
|
+
enhancedError.resourceId = id;
|
|
9267
|
+
enhancedError.resourceKey = key;
|
|
9268
|
+
throw enhancedError;
|
|
9189
9269
|
}
|
|
9190
|
-
const { metadata: processedMetadata } = await behaviorImpl.handleGet({
|
|
9191
|
-
resource: this,
|
|
9192
|
-
metadata,
|
|
9193
|
-
body
|
|
9194
|
-
});
|
|
9195
|
-
let data = processedMetadata;
|
|
9196
|
-
data.id = id;
|
|
9197
|
-
data._contentLength = request.ContentLength;
|
|
9198
|
-
data._lastModified = request.LastModified;
|
|
9199
|
-
data._hasContent = request.ContentLength > 0;
|
|
9200
|
-
data._mimeType = request.ContentType || null;
|
|
9201
|
-
if (request.VersionId) data._versionId = request.VersionId;
|
|
9202
|
-
if (request.Expiration) data._expiresAt = request.Expiration;
|
|
9203
|
-
data._definitionHash = this.getDefinitionHash();
|
|
9204
|
-
this.emit("get", data);
|
|
9205
|
-
return data;
|
|
9206
9270
|
}
|
|
9271
|
+
/**
|
|
9272
|
+
* Check if a resource exists by ID
|
|
9273
|
+
* @param {string} id - Resource ID
|
|
9274
|
+
* @returns {Promise<boolean>} True if resource exists, false otherwise
|
|
9275
|
+
* @example
|
|
9276
|
+
* const exists = await resource.exists('user-123');
|
|
9277
|
+
* if (exists) {
|
|
9278
|
+
* console.log('User exists');
|
|
9279
|
+
* }
|
|
9280
|
+
*/
|
|
9207
9281
|
async exists(id) {
|
|
9208
9282
|
try {
|
|
9209
9283
|
const key = this.getResourceKey(id);
|
|
@@ -9213,6 +9287,24 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9213
9287
|
return false;
|
|
9214
9288
|
}
|
|
9215
9289
|
}
|
|
9290
|
+
/**
|
|
9291
|
+
* Update an existing resource object
|
|
9292
|
+
* @param {string} id - Resource ID
|
|
9293
|
+
* @param {Object} attributes - Attributes to update (partial update supported)
|
|
9294
|
+
* @returns {Promise<Object>} The updated resource object with all attributes
|
|
9295
|
+
* @example
|
|
9296
|
+
* // Update specific fields
|
|
9297
|
+
* const updatedUser = await resource.update('user-123', {
|
|
9298
|
+
* name: 'John Updated',
|
|
9299
|
+
* age: 31
|
|
9300
|
+
* });
|
|
9301
|
+
*
|
|
9302
|
+
* // Update with timestamps (if enabled)
|
|
9303
|
+
* const updatedUser = await resource.update('user-123', {
|
|
9304
|
+
* email: 'newemail@example.com'
|
|
9305
|
+
* });
|
|
9306
|
+
* console.log(updatedUser.updatedAt); // ISO timestamp
|
|
9307
|
+
*/
|
|
9216
9308
|
async update(id, attributes) {
|
|
9217
9309
|
const live = await this.get(id);
|
|
9218
9310
|
if (this.options.timestamps) {
|
|
@@ -9269,6 +9361,14 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9269
9361
|
this.emit("update", preProcessedData, validated);
|
|
9270
9362
|
return validated;
|
|
9271
9363
|
}
|
|
9364
|
+
/**
|
|
9365
|
+
* Delete a resource object by ID
|
|
9366
|
+
* @param {string} id - Resource ID
|
|
9367
|
+
* @returns {Promise<Object>} S3 delete response
|
|
9368
|
+
* @example
|
|
9369
|
+
* await resource.delete('user-123');
|
|
9370
|
+
* console.log('User deleted successfully');
|
|
9371
|
+
*/
|
|
9272
9372
|
async delete(id) {
|
|
9273
9373
|
let objectData;
|
|
9274
9374
|
try {
|
|
@@ -9283,6 +9383,20 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9283
9383
|
this.emit("delete", id);
|
|
9284
9384
|
return response;
|
|
9285
9385
|
}
|
|
9386
|
+
/**
|
|
9387
|
+
* Insert or update a resource object (upsert operation)
|
|
9388
|
+
* @param {Object} params - Upsert parameters
|
|
9389
|
+
* @param {string} params.id - Resource ID (required for upsert)
|
|
9390
|
+
* @param {...Object} params - Resource attributes (any additional properties)
|
|
9391
|
+
* @returns {Promise<Object>} The inserted or updated resource object
|
|
9392
|
+
* @example
|
|
9393
|
+
* // Will insert if doesn't exist, update if exists
|
|
9394
|
+
* const user = await resource.upsert({
|
|
9395
|
+
* id: 'user-123',
|
|
9396
|
+
* name: 'John Doe',
|
|
9397
|
+
* email: 'john@example.com'
|
|
9398
|
+
* });
|
|
9399
|
+
*/
|
|
9286
9400
|
async upsert({ id, ...attributes }) {
|
|
9287
9401
|
const exists = await this.exists(id);
|
|
9288
9402
|
if (exists) {
|
|
@@ -9290,6 +9404,28 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9290
9404
|
}
|
|
9291
9405
|
return this.insert({ id, ...attributes });
|
|
9292
9406
|
}
|
|
9407
|
+
/**
|
|
9408
|
+
* Count resources with optional partition filtering
|
|
9409
|
+
* @param {Object} [params] - Count parameters
|
|
9410
|
+
* @param {string} [params.partition] - Partition name to count in
|
|
9411
|
+
* @param {Object} [params.partitionValues] - Partition field values to filter by
|
|
9412
|
+
* @returns {Promise<number>} Total count of matching resources
|
|
9413
|
+
* @example
|
|
9414
|
+
* // Count all resources
|
|
9415
|
+
* const total = await resource.count();
|
|
9416
|
+
*
|
|
9417
|
+
* // Count in specific partition
|
|
9418
|
+
* const googleUsers = await resource.count({
|
|
9419
|
+
* partition: 'byUtmSource',
|
|
9420
|
+
* partitionValues: { 'utm.source': 'google' }
|
|
9421
|
+
* });
|
|
9422
|
+
*
|
|
9423
|
+
* // Count in multi-field partition
|
|
9424
|
+
* const usElectronics = await resource.count({
|
|
9425
|
+
* partition: 'byCategoryRegion',
|
|
9426
|
+
* partitionValues: { category: 'electronics', region: 'US' }
|
|
9427
|
+
* });
|
|
9428
|
+
*/
|
|
9293
9429
|
async count({ partition = null, partitionValues = {} } = {}) {
|
|
9294
9430
|
let prefix;
|
|
9295
9431
|
if (partition && Object.keys(partitionValues).length > 0) {
|
|
@@ -9320,6 +9456,19 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9320
9456
|
this.emit("count", count);
|
|
9321
9457
|
return count;
|
|
9322
9458
|
}
|
|
9459
|
+
/**
|
|
9460
|
+
* Insert multiple resources in parallel
|
|
9461
|
+
* @param {Object[]} objects - Array of resource objects to insert
|
|
9462
|
+
* @returns {Promise<Object[]>} Array of inserted resource objects
|
|
9463
|
+
* @example
|
|
9464
|
+
* const users = [
|
|
9465
|
+
* { name: 'John', email: 'john@example.com' },
|
|
9466
|
+
* { name: 'Jane', email: 'jane@example.com' },
|
|
9467
|
+
* { name: 'Bob', email: 'bob@example.com' }
|
|
9468
|
+
* ];
|
|
9469
|
+
* const insertedUsers = await resource.insertMany(users);
|
|
9470
|
+
* console.log(`Inserted ${insertedUsers.length} users`);
|
|
9471
|
+
*/
|
|
9323
9472
|
async insertMany(objects) {
|
|
9324
9473
|
const { results } = await promisePool.PromisePool.for(objects).withConcurrency(this.parallelism).handleError(async (error, content) => {
|
|
9325
9474
|
this.emit("error", error, content);
|
|
@@ -9331,6 +9480,15 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9331
9480
|
this.emit("insertMany", objects.length);
|
|
9332
9481
|
return results;
|
|
9333
9482
|
}
|
|
9483
|
+
/**
|
|
9484
|
+
* Delete multiple resources by their IDs in parallel
|
|
9485
|
+
* @param {string[]} ids - Array of resource IDs to delete
|
|
9486
|
+
* @returns {Promise<Object[]>} Array of S3 delete responses
|
|
9487
|
+
* @example
|
|
9488
|
+
* const deletedIds = ['user-1', 'user-2', 'user-3'];
|
|
9489
|
+
* const results = await resource.deleteMany(deletedIds);
|
|
9490
|
+
* console.log(`Deleted ${deletedIds.length} users`);
|
|
9491
|
+
*/
|
|
9334
9492
|
async deleteMany(ids) {
|
|
9335
9493
|
const packages = lodashEs.chunk(
|
|
9336
9494
|
ids.map((id) => this.getResourceKey(id)),
|
|
@@ -9389,7 +9547,35 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9389
9547
|
});
|
|
9390
9548
|
return { deletedCount, resource: this.name };
|
|
9391
9549
|
}
|
|
9392
|
-
|
|
9550
|
+
/**
|
|
9551
|
+
* List resource IDs with optional partition filtering and pagination
|
|
9552
|
+
* @param {Object} [params] - List parameters
|
|
9553
|
+
* @param {string} [params.partition] - Partition name to list from
|
|
9554
|
+
* @param {Object} [params.partitionValues] - Partition field values to filter by
|
|
9555
|
+
* @param {number} [params.limit] - Maximum number of results to return
|
|
9556
|
+
* @param {number} [params.offset=0] - Offset for pagination
|
|
9557
|
+
* @returns {Promise<string[]>} Array of resource IDs (strings)
|
|
9558
|
+
* @example
|
|
9559
|
+
* // List all IDs
|
|
9560
|
+
* const allIds = await resource.listIds();
|
|
9561
|
+
*
|
|
9562
|
+
* // List IDs with pagination
|
|
9563
|
+
* const firstPageIds = await resource.listIds({ limit: 10, offset: 0 });
|
|
9564
|
+
* const secondPageIds = await resource.listIds({ limit: 10, offset: 10 });
|
|
9565
|
+
*
|
|
9566
|
+
* // List IDs from specific partition
|
|
9567
|
+
* const googleUserIds = await resource.listIds({
|
|
9568
|
+
* partition: 'byUtmSource',
|
|
9569
|
+
* partitionValues: { 'utm.source': 'google' }
|
|
9570
|
+
* });
|
|
9571
|
+
*
|
|
9572
|
+
* // List IDs from multi-field partition
|
|
9573
|
+
* const usElectronicsIds = await resource.listIds({
|
|
9574
|
+
* partition: 'byCategoryRegion',
|
|
9575
|
+
* partitionValues: { category: 'electronics', region: 'US' }
|
|
9576
|
+
* });
|
|
9577
|
+
*/
|
|
9578
|
+
async listIds({ partition = null, partitionValues = {}, limit, offset = 0 } = {}) {
|
|
9393
9579
|
let prefix;
|
|
9394
9580
|
if (partition && Object.keys(partitionValues).length > 0) {
|
|
9395
9581
|
const partitionDef = this.options.partitions[partition];
|
|
@@ -9413,8 +9599,11 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9413
9599
|
} else {
|
|
9414
9600
|
prefix = `resource=${this.name}/v=${this.version}`;
|
|
9415
9601
|
}
|
|
9416
|
-
const keys = await this.client.
|
|
9417
|
-
prefix
|
|
9602
|
+
const keys = await this.client.getKeysPage({
|
|
9603
|
+
prefix,
|
|
9604
|
+
offset,
|
|
9605
|
+
amount: limit || 1e3
|
|
9606
|
+
// Default to 1000 if no limit specified
|
|
9418
9607
|
});
|
|
9419
9608
|
const ids = keys.map((key) => {
|
|
9420
9609
|
const parts = key.split("/");
|
|
@@ -9425,13 +9614,36 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9425
9614
|
return ids;
|
|
9426
9615
|
}
|
|
9427
9616
|
/**
|
|
9428
|
-
* List objects
|
|
9429
|
-
* @param {Object}
|
|
9430
|
-
* @param {
|
|
9431
|
-
* @
|
|
9617
|
+
* List resource objects with optional partition filtering and pagination
|
|
9618
|
+
* @param {Object} [params] - List parameters
|
|
9619
|
+
* @param {string} [params.partition] - Partition name to list from
|
|
9620
|
+
* @param {Object} [params.partitionValues] - Partition field values to filter by
|
|
9621
|
+
* @param {number} [params.limit] - Maximum number of results to return
|
|
9622
|
+
* @param {number} [params.offset=0] - Offset for pagination
|
|
9623
|
+
* @returns {Promise<Object[]>} Array of resource objects with all attributes
|
|
9624
|
+
* @example
|
|
9625
|
+
* // List all resources
|
|
9626
|
+
* const allUsers = await resource.list();
|
|
9627
|
+
*
|
|
9628
|
+
* // List with pagination
|
|
9629
|
+
* const firstPage = await resource.list({ limit: 10, offset: 0 });
|
|
9630
|
+
* const secondPage = await resource.list({ limit: 10, offset: 10 });
|
|
9631
|
+
*
|
|
9632
|
+
* // List from specific partition
|
|
9633
|
+
* const googleUsers = await resource.list({
|
|
9634
|
+
* partition: 'byUtmSource',
|
|
9635
|
+
* partitionValues: { 'utm.source': 'google' }
|
|
9636
|
+
* });
|
|
9637
|
+
*
|
|
9638
|
+
* // List from partition with pagination
|
|
9639
|
+
* const googleUsersPage = await resource.list({
|
|
9640
|
+
* partition: 'byUtmSource',
|
|
9641
|
+
* partitionValues: { 'utm.source': 'google' },
|
|
9642
|
+
* limit: 5,
|
|
9643
|
+
* offset: 0
|
|
9644
|
+
* });
|
|
9432
9645
|
*/
|
|
9433
|
-
async
|
|
9434
|
-
const { limit, offset = 0 } = options;
|
|
9646
|
+
async list({ partition = null, partitionValues = {}, limit, offset = 0 } = {}) {
|
|
9435
9647
|
if (!partition) {
|
|
9436
9648
|
const ids2 = await this.listIds({ partition, partitionValues });
|
|
9437
9649
|
let filteredIds2 = ids2.slice(offset);
|
|
@@ -9441,7 +9653,7 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9441
9653
|
const { results: results2 } = await promisePool.PromisePool.for(filteredIds2).withConcurrency(this.parallelism).process(async (id) => {
|
|
9442
9654
|
return await this.get(id);
|
|
9443
9655
|
});
|
|
9444
|
-
this.emit("
|
|
9656
|
+
this.emit("list", { partition, partitionValues, count: results2.length });
|
|
9445
9657
|
return results2;
|
|
9446
9658
|
}
|
|
9447
9659
|
const partitionDef = this.options.partitions[partition];
|
|
@@ -9474,11 +9686,19 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9474
9686
|
filteredIds = filteredIds.slice(0, limit);
|
|
9475
9687
|
}
|
|
9476
9688
|
const { results } = await promisePool.PromisePool.for(filteredIds).withConcurrency(this.parallelism).process(async (id) => {
|
|
9477
|
-
return await this.getFromPartition(id, partition, partitionValues);
|
|
9689
|
+
return await this.getFromPartition({ id, partitionName: partition, partitionValues });
|
|
9478
9690
|
});
|
|
9479
|
-
this.emit("
|
|
9691
|
+
this.emit("list", { partition, partitionValues, count: results.length });
|
|
9480
9692
|
return results;
|
|
9481
9693
|
}
|
|
9694
|
+
/**
|
|
9695
|
+
* Get multiple resources by their IDs
|
|
9696
|
+
* @param {string[]} ids - Array of resource IDs
|
|
9697
|
+
* @returns {Promise<Object[]>} Array of resource objects
|
|
9698
|
+
* @example
|
|
9699
|
+
* const users = await resource.getMany(['user-1', 'user-2', 'user-3']);
|
|
9700
|
+
* users.forEach(user => console.log(user.name));
|
|
9701
|
+
*/
|
|
9482
9702
|
async getMany(ids) {
|
|
9483
9703
|
const { results } = await promisePool.PromisePool.for(ids).withConcurrency(this.client.parallelism).process(async (id) => {
|
|
9484
9704
|
this.emit("id", id);
|
|
@@ -9489,6 +9709,13 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9489
9709
|
this.emit("getMany", ids.length);
|
|
9490
9710
|
return results;
|
|
9491
9711
|
}
|
|
9712
|
+
/**
|
|
9713
|
+
* Get all resources (equivalent to list() without pagination)
|
|
9714
|
+
* @returns {Promise<Object[]>} Array of all resource objects
|
|
9715
|
+
* @example
|
|
9716
|
+
* const allUsers = await resource.getAll();
|
|
9717
|
+
* console.log(`Total users: ${allUsers.length}`);
|
|
9718
|
+
*/
|
|
9492
9719
|
async getAll() {
|
|
9493
9720
|
let ids = await this.listIds();
|
|
9494
9721
|
if (ids.length === 0) return [];
|
|
@@ -9499,20 +9726,65 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9499
9726
|
this.emit("getAll", results.length);
|
|
9500
9727
|
return results;
|
|
9501
9728
|
}
|
|
9502
|
-
|
|
9503
|
-
|
|
9504
|
-
|
|
9505
|
-
|
|
9506
|
-
|
|
9507
|
-
|
|
9508
|
-
|
|
9509
|
-
|
|
9729
|
+
/**
|
|
9730
|
+
* Get a page of resources with pagination metadata
|
|
9731
|
+
* @param {Object} [params] - Page parameters
|
|
9732
|
+
* @param {number} [params.offset=0] - Offset for pagination
|
|
9733
|
+
* @param {number} [params.size=100] - Page size
|
|
9734
|
+
* @param {string} [params.partition] - Partition name to page from
|
|
9735
|
+
* @param {Object} [params.partitionValues] - Partition field values to filter by
|
|
9736
|
+
* @param {boolean} [params.skipCount=false] - Skip total count for performance (useful for large collections)
|
|
9737
|
+
* @returns {Promise<Object>} Page result with items and pagination info
|
|
9738
|
+
* @example
|
|
9739
|
+
* // Get first page of all resources
|
|
9740
|
+
* const page = await resource.page({ offset: 0, size: 10 });
|
|
9741
|
+
* console.log(`Page ${page.page + 1} of ${page.totalPages}`);
|
|
9742
|
+
* console.log(`Showing ${page.items.length} of ${page.totalItems} total`);
|
|
9743
|
+
*
|
|
9744
|
+
* // Get page from specific partition
|
|
9745
|
+
* const googlePage = await resource.page({
|
|
9746
|
+
* partition: 'byUtmSource',
|
|
9747
|
+
* partitionValues: { 'utm.source': 'google' },
|
|
9748
|
+
* offset: 0,
|
|
9749
|
+
* size: 5
|
|
9750
|
+
* });
|
|
9751
|
+
*
|
|
9752
|
+
* // Skip count for performance in large collections
|
|
9753
|
+
* const fastPage = await resource.page({
|
|
9754
|
+
* offset: 0,
|
|
9755
|
+
* size: 100,
|
|
9756
|
+
* skipCount: true
|
|
9757
|
+
* });
|
|
9758
|
+
* console.log(`Got ${fastPage.items.length} items`); // totalItems will be null
|
|
9759
|
+
*/
|
|
9760
|
+
async page({ offset = 0, size = 100, partition = null, partitionValues = {}, skipCount = false } = {}) {
|
|
9761
|
+
let totalItems = null;
|
|
9762
|
+
let totalPages = null;
|
|
9763
|
+
if (!skipCount) {
|
|
9764
|
+
totalItems = await this.count({ partition, partitionValues });
|
|
9765
|
+
totalPages = Math.ceil(totalItems / size);
|
|
9766
|
+
}
|
|
9767
|
+
const page = Math.floor(offset / size);
|
|
9768
|
+
const items = await this.list({
|
|
9769
|
+
partition,
|
|
9770
|
+
partitionValues,
|
|
9771
|
+
limit: size,
|
|
9772
|
+
offset
|
|
9773
|
+
});
|
|
9510
9774
|
const result = {
|
|
9511
9775
|
items,
|
|
9512
9776
|
totalItems,
|
|
9513
|
-
page
|
|
9777
|
+
page,
|
|
9514
9778
|
pageSize: size,
|
|
9515
|
-
totalPages
|
|
9779
|
+
totalPages,
|
|
9780
|
+
// Add additional metadata for debugging
|
|
9781
|
+
_debug: {
|
|
9782
|
+
requestedSize: size,
|
|
9783
|
+
requestedOffset: offset,
|
|
9784
|
+
actualItemsReturned: items.length,
|
|
9785
|
+
skipCount,
|
|
9786
|
+
hasTotalItems: totalItems !== null
|
|
9787
|
+
}
|
|
9516
9788
|
};
|
|
9517
9789
|
this.emit("page", result);
|
|
9518
9790
|
return result;
|
|
@@ -9526,36 +9798,62 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9526
9798
|
return stream.build();
|
|
9527
9799
|
}
|
|
9528
9800
|
/**
|
|
9529
|
-
*
|
|
9530
|
-
* @param {
|
|
9531
|
-
* @param {
|
|
9532
|
-
* @param {string}
|
|
9801
|
+
* Set binary content for a resource
|
|
9802
|
+
* @param {Object} params - Content parameters
|
|
9803
|
+
* @param {string} params.id - Resource ID
|
|
9804
|
+
* @param {Buffer|string} params.buffer - Content buffer or string
|
|
9805
|
+
* @param {string} [params.contentType='application/octet-stream'] - Content type
|
|
9806
|
+
* @returns {Promise<Object>} Updated resource data
|
|
9807
|
+
* @example
|
|
9808
|
+
* // Set image content
|
|
9809
|
+
* const imageBuffer = fs.readFileSync('image.jpg');
|
|
9810
|
+
* await resource.setContent({
|
|
9811
|
+
* id: 'user-123',
|
|
9812
|
+
* buffer: imageBuffer,
|
|
9813
|
+
* contentType: 'image/jpeg'
|
|
9814
|
+
* });
|
|
9815
|
+
*
|
|
9816
|
+
* // Set text content
|
|
9817
|
+
* await resource.setContent({
|
|
9818
|
+
* id: 'document-456',
|
|
9819
|
+
* buffer: 'Hello World',
|
|
9820
|
+
* contentType: 'text/plain'
|
|
9821
|
+
* });
|
|
9533
9822
|
*/
|
|
9534
|
-
async setContent(id, buffer, contentType = "application/octet-stream") {
|
|
9535
|
-
|
|
9536
|
-
|
|
9537
|
-
|
|
9538
|
-
|
|
9539
|
-
|
|
9540
|
-
|
|
9541
|
-
|
|
9542
|
-
|
|
9543
|
-
|
|
9544
|
-
}
|
|
9545
|
-
|
|
9546
|
-
key,
|
|
9823
|
+
async setContent({ id, buffer, contentType = "application/octet-stream" }) {
|
|
9824
|
+
const currentData = await this.get(id);
|
|
9825
|
+
if (!currentData) {
|
|
9826
|
+
throw new Error(`Resource with id '${id}' not found`);
|
|
9827
|
+
}
|
|
9828
|
+
const updatedData = {
|
|
9829
|
+
...currentData,
|
|
9830
|
+
_hasContent: true,
|
|
9831
|
+
_contentLength: buffer.length,
|
|
9832
|
+
_mimeType: contentType
|
|
9833
|
+
};
|
|
9834
|
+
await this.client.putObject({
|
|
9835
|
+
key: this.getResourceKey(id),
|
|
9836
|
+
metadata: await this.schema.mapper(updatedData),
|
|
9547
9837
|
body: buffer,
|
|
9548
|
-
contentType
|
|
9549
|
-
metadata: existingMetadata
|
|
9550
|
-
// Preserve existing metadata
|
|
9838
|
+
contentType
|
|
9551
9839
|
});
|
|
9552
|
-
this.emit("setContent", id, buffer.length
|
|
9553
|
-
return
|
|
9840
|
+
this.emit("setContent", { id, contentType, contentLength: buffer.length });
|
|
9841
|
+
return updatedData;
|
|
9554
9842
|
}
|
|
9555
9843
|
/**
|
|
9556
9844
|
* Retrieve binary content associated with a resource
|
|
9557
9845
|
* @param {string} id - Resource ID
|
|
9558
|
-
* @returns {Object} Object with buffer and contentType
|
|
9846
|
+
* @returns {Promise<Object>} Object with buffer and contentType
|
|
9847
|
+
* @example
|
|
9848
|
+
* const content = await resource.content('user-123');
|
|
9849
|
+
* if (content.buffer) {
|
|
9850
|
+
* console.log('Content type:', content.contentType);
|
|
9851
|
+
* console.log('Content size:', content.buffer.length);
|
|
9852
|
+
* // Save to file
|
|
9853
|
+
* fs.writeFileSync('output.jpg', content.buffer);
|
|
9854
|
+
* } else {
|
|
9855
|
+
* console.log('No content found');
|
|
9856
|
+
* }
|
|
9559
9857
|
*/
|
|
9560
9858
|
async content(id) {
|
|
9561
9859
|
const key = this.getResourceKey(id);
|
|
@@ -9650,7 +9948,7 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9650
9948
|
return;
|
|
9651
9949
|
}
|
|
9652
9950
|
for (const [partitionName, partition] of Object.entries(partitions)) {
|
|
9653
|
-
const partitionKey = this.getPartitionKey(partitionName, data.id, data);
|
|
9951
|
+
const partitionKey = this.getPartitionKey({ partitionName, id: data.id, data });
|
|
9654
9952
|
if (partitionKey) {
|
|
9655
9953
|
const mappedData = await this.schema.mapper(data);
|
|
9656
9954
|
const behaviorImpl = getBehavior(this.behavior);
|
|
@@ -9682,7 +9980,7 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9682
9980
|
}
|
|
9683
9981
|
const keysToDelete = [];
|
|
9684
9982
|
for (const [partitionName, partition] of Object.entries(partitions)) {
|
|
9685
|
-
const partitionKey = this.getPartitionKey(partitionName, data.id, data);
|
|
9983
|
+
const partitionKey = this.getPartitionKey({ partitionName, id: data.id, data });
|
|
9686
9984
|
if (partitionKey) {
|
|
9687
9985
|
keysToDelete.push(partitionKey);
|
|
9688
9986
|
}
|
|
@@ -9696,20 +9994,72 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9696
9994
|
}
|
|
9697
9995
|
}
|
|
9698
9996
|
/**
|
|
9699
|
-
* Query
|
|
9700
|
-
* @param {Object} filter - Filter criteria
|
|
9701
|
-
* @
|
|
9997
|
+
* Query resources with simple filtering and pagination
|
|
9998
|
+
* @param {Object} [filter={}] - Filter criteria (exact field matches)
|
|
9999
|
+
* @param {Object} [options] - Query options
|
|
10000
|
+
* @param {number} [options.limit=100] - Maximum number of results
|
|
10001
|
+
* @param {number} [options.offset=0] - Offset for pagination
|
|
10002
|
+
* @param {string} [options.partition] - Partition name to query from
|
|
10003
|
+
* @param {Object} [options.partitionValues] - Partition field values to filter by
|
|
10004
|
+
* @returns {Promise<Object[]>} Array of filtered resource objects
|
|
10005
|
+
* @example
|
|
10006
|
+
* // Query all resources (no filter)
|
|
10007
|
+
* const allUsers = await resource.query();
|
|
10008
|
+
*
|
|
10009
|
+
* // Query with simple filter
|
|
10010
|
+
* const activeUsers = await resource.query({ status: 'active' });
|
|
10011
|
+
*
|
|
10012
|
+
* // Query with multiple filters
|
|
10013
|
+
* const usElectronics = await resource.query({
|
|
10014
|
+
* category: 'electronics',
|
|
10015
|
+
* region: 'US'
|
|
10016
|
+
* });
|
|
10017
|
+
*
|
|
10018
|
+
* // Query with pagination
|
|
10019
|
+
* const firstPage = await resource.query(
|
|
10020
|
+
* { status: 'active' },
|
|
10021
|
+
* { limit: 10, offset: 0 }
|
|
10022
|
+
* );
|
|
10023
|
+
*
|
|
10024
|
+
* // Query within partition
|
|
10025
|
+
* const googleUsers = await resource.query(
|
|
10026
|
+
* { status: 'active' },
|
|
10027
|
+
* {
|
|
10028
|
+
* partition: 'byUtmSource',
|
|
10029
|
+
* partitionValues: { 'utm.source': 'google' },
|
|
10030
|
+
* limit: 5
|
|
10031
|
+
* }
|
|
10032
|
+
* );
|
|
9702
10033
|
*/
|
|
9703
|
-
async query(filter = {}) {
|
|
9704
|
-
const allDocuments = await this.getAll();
|
|
10034
|
+
async query(filter = {}, { limit = 100, offset = 0, partition = null, partitionValues = {} } = {}) {
|
|
9705
10035
|
if (Object.keys(filter).length === 0) {
|
|
9706
|
-
return
|
|
9707
|
-
}
|
|
9708
|
-
|
|
9709
|
-
|
|
9710
|
-
|
|
10036
|
+
return await this.list({ partition, partitionValues, limit, offset });
|
|
10037
|
+
}
|
|
10038
|
+
const results = [];
|
|
10039
|
+
let currentOffset = offset;
|
|
10040
|
+
const batchSize = Math.min(limit, 50);
|
|
10041
|
+
while (results.length < limit) {
|
|
10042
|
+
const batch = await this.list({
|
|
10043
|
+
partition,
|
|
10044
|
+
partitionValues,
|
|
10045
|
+
limit: batchSize,
|
|
10046
|
+
offset: currentOffset
|
|
9711
10047
|
});
|
|
9712
|
-
|
|
10048
|
+
if (batch.length === 0) {
|
|
10049
|
+
break;
|
|
10050
|
+
}
|
|
10051
|
+
const filteredBatch = batch.filter((doc) => {
|
|
10052
|
+
return Object.entries(filter).every(([key, value]) => {
|
|
10053
|
+
return doc[key] === value;
|
|
10054
|
+
});
|
|
10055
|
+
});
|
|
10056
|
+
results.push(...filteredBatch);
|
|
10057
|
+
currentOffset += batchSize;
|
|
10058
|
+
if (batch.length < batchSize) {
|
|
10059
|
+
break;
|
|
10060
|
+
}
|
|
10061
|
+
}
|
|
10062
|
+
return results.slice(0, limit);
|
|
9713
10063
|
}
|
|
9714
10064
|
/**
|
|
9715
10065
|
* Update partition objects to keep them in sync
|
|
@@ -9721,7 +10071,7 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9721
10071
|
return;
|
|
9722
10072
|
}
|
|
9723
10073
|
for (const [partitionName, partition] of Object.entries(partitions)) {
|
|
9724
|
-
const partitionKey = this.getPartitionKey(partitionName, data.id, data);
|
|
10074
|
+
const partitionKey = this.getPartitionKey({ partitionName, id: data.id, data });
|
|
9725
10075
|
if (partitionKey) {
|
|
9726
10076
|
const mappedData = await this.schema.mapper(data);
|
|
9727
10077
|
const behaviorImpl = getBehavior(this.behavior);
|
|
@@ -9748,13 +10098,30 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9748
10098
|
}
|
|
9749
10099
|
}
|
|
9750
10100
|
/**
|
|
9751
|
-
* Get object directly from a specific partition
|
|
9752
|
-
* @param {
|
|
9753
|
-
* @param {string}
|
|
9754
|
-
* @param {
|
|
9755
|
-
* @
|
|
10101
|
+
* Get a resource object directly from a specific partition
|
|
10102
|
+
* @param {Object} params - Partition parameters
|
|
10103
|
+
* @param {string} params.id - Resource ID
|
|
10104
|
+
* @param {string} params.partitionName - Name of the partition
|
|
10105
|
+
* @param {Object} params.partitionValues - Values for partition fields
|
|
10106
|
+
* @returns {Promise<Object>} The resource object with partition metadata
|
|
10107
|
+
* @example
|
|
10108
|
+
* // Get user from UTM source partition
|
|
10109
|
+
* const user = await resource.getFromPartition({
|
|
10110
|
+
* id: 'user-123',
|
|
10111
|
+
* partitionName: 'byUtmSource',
|
|
10112
|
+
* partitionValues: { 'utm.source': 'google' }
|
|
10113
|
+
* });
|
|
10114
|
+
* console.log(user._partition); // 'byUtmSource'
|
|
10115
|
+
* console.log(user._partitionValues); // { 'utm.source': 'google' }
|
|
10116
|
+
*
|
|
10117
|
+
* // Get product from multi-field partition
|
|
10118
|
+
* const product = await resource.getFromPartition({
|
|
10119
|
+
* id: 'product-456',
|
|
10120
|
+
* partitionName: 'byCategoryRegion',
|
|
10121
|
+
* partitionValues: { category: 'electronics', region: 'US' }
|
|
10122
|
+
* });
|
|
9756
10123
|
*/
|
|
9757
|
-
async getFromPartition(id, partitionName, partitionValues = {}) {
|
|
10124
|
+
async getFromPartition({ id, partitionName, partitionValues = {} }) {
|
|
9758
10125
|
const partition = this.options.partitions[partitionName];
|
|
9759
10126
|
if (!partition) {
|
|
9760
10127
|
throw new Error(`Partition '${partitionName}' not found`);
|
|
@@ -9813,7 +10180,7 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9813
10180
|
this.version = "1";
|
|
9814
10181
|
this.s3dbVersion = (() => {
|
|
9815
10182
|
try {
|
|
9816
|
-
return true ? "4.1.
|
|
10183
|
+
return true ? "4.1.7" : "latest";
|
|
9817
10184
|
} catch (e) {
|
|
9818
10185
|
return "latest";
|
|
9819
10186
|
}
|