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.es.js
CHANGED
|
@@ -8566,6 +8566,14 @@ function calculateUTF8Bytes(str) {
|
|
|
8566
8566
|
}
|
|
8567
8567
|
return bytes;
|
|
8568
8568
|
}
|
|
8569
|
+
function calculateAttributeNamesSize(mappedObject) {
|
|
8570
|
+
let totalSize = 0;
|
|
8571
|
+
for (const key of Object.keys(mappedObject)) {
|
|
8572
|
+
if (key === "_v") continue;
|
|
8573
|
+
totalSize += calculateUTF8Bytes(key);
|
|
8574
|
+
}
|
|
8575
|
+
return totalSize;
|
|
8576
|
+
}
|
|
8569
8577
|
function transformValue(value) {
|
|
8570
8578
|
if (value === null || value === void 0) {
|
|
8571
8579
|
return "";
|
|
@@ -8600,49 +8608,31 @@ function calculateAttributeSizes(mappedObject) {
|
|
|
8600
8608
|
return sizes;
|
|
8601
8609
|
}
|
|
8602
8610
|
function calculateTotalSize(mappedObject) {
|
|
8603
|
-
const
|
|
8604
|
-
|
|
8611
|
+
const valueSizes = calculateAttributeSizes(mappedObject);
|
|
8612
|
+
const valueTotal = Object.values(valueSizes).reduce((total, size) => total + size, 0);
|
|
8613
|
+
const namesSize = calculateAttributeNamesSize(mappedObject);
|
|
8614
|
+
return valueTotal + namesSize;
|
|
8605
8615
|
}
|
|
8606
8616
|
|
|
8607
|
-
const S3_METADATA_LIMIT_BYTES
|
|
8617
|
+
const S3_METADATA_LIMIT_BYTES = 2048;
|
|
8608
8618
|
async function handleInsert$3({ resource, data, mappedData }) {
|
|
8609
8619
|
const totalSize = calculateTotalSize(mappedData);
|
|
8610
|
-
if (totalSize > S3_METADATA_LIMIT_BYTES
|
|
8611
|
-
|
|
8612
|
-
operation: "insert",
|
|
8613
|
-
totalSize,
|
|
8614
|
-
limit: S3_METADATA_LIMIT_BYTES$3,
|
|
8615
|
-
excess: totalSize - S3_METADATA_LIMIT_BYTES$3,
|
|
8616
|
-
data
|
|
8617
|
-
});
|
|
8620
|
+
if (totalSize > S3_METADATA_LIMIT_BYTES) {
|
|
8621
|
+
throw new Error(`S3 metadata size exceeds 2KB limit. Current size: ${totalSize} bytes, limit: ${S3_METADATA_LIMIT_BYTES} bytes`);
|
|
8618
8622
|
}
|
|
8619
8623
|
return { mappedData, body: "" };
|
|
8620
8624
|
}
|
|
8621
8625
|
async function handleUpdate$3({ resource, id, data, mappedData }) {
|
|
8622
8626
|
const totalSize = calculateTotalSize(mappedData);
|
|
8623
|
-
if (totalSize > S3_METADATA_LIMIT_BYTES
|
|
8624
|
-
|
|
8625
|
-
operation: "update",
|
|
8626
|
-
id,
|
|
8627
|
-
totalSize,
|
|
8628
|
-
limit: S3_METADATA_LIMIT_BYTES$3,
|
|
8629
|
-
excess: totalSize - S3_METADATA_LIMIT_BYTES$3,
|
|
8630
|
-
data
|
|
8631
|
-
});
|
|
8627
|
+
if (totalSize > S3_METADATA_LIMIT_BYTES) {
|
|
8628
|
+
throw new Error(`S3 metadata size exceeds 2KB limit. Current size: ${totalSize} bytes, limit: ${S3_METADATA_LIMIT_BYTES} bytes`);
|
|
8632
8629
|
}
|
|
8633
8630
|
return { mappedData, body: "" };
|
|
8634
8631
|
}
|
|
8635
8632
|
async function handleUpsert$3({ resource, id, data, mappedData }) {
|
|
8636
8633
|
const totalSize = calculateTotalSize(mappedData);
|
|
8637
|
-
if (totalSize > S3_METADATA_LIMIT_BYTES
|
|
8638
|
-
|
|
8639
|
-
operation: "upsert",
|
|
8640
|
-
id,
|
|
8641
|
-
totalSize,
|
|
8642
|
-
limit: S3_METADATA_LIMIT_BYTES$3,
|
|
8643
|
-
excess: totalSize - S3_METADATA_LIMIT_BYTES$3,
|
|
8644
|
-
data
|
|
8645
|
-
});
|
|
8634
|
+
if (totalSize > S3_METADATA_LIMIT_BYTES) {
|
|
8635
|
+
throw new Error(`S3 metadata size exceeds 2KB limit. Current size: ${totalSize} bytes, limit: ${S3_METADATA_LIMIT_BYTES} bytes`);
|
|
8646
8636
|
}
|
|
8647
8637
|
return { mappedData, body: "" };
|
|
8648
8638
|
}
|
|
@@ -8650,33 +8640,53 @@ async function handleGet$3({ resource, metadata, body }) {
|
|
|
8650
8640
|
return { metadata, body };
|
|
8651
8641
|
}
|
|
8652
8642
|
|
|
8653
|
-
var
|
|
8643
|
+
var enforceLimits = /*#__PURE__*/Object.freeze({
|
|
8654
8644
|
__proto__: null,
|
|
8645
|
+
S3_METADATA_LIMIT_BYTES: S3_METADATA_LIMIT_BYTES,
|
|
8655
8646
|
handleGet: handleGet$3,
|
|
8656
8647
|
handleInsert: handleInsert$3,
|
|
8657
8648
|
handleUpdate: handleUpdate$3,
|
|
8658
8649
|
handleUpsert: handleUpsert$3
|
|
8659
8650
|
});
|
|
8660
8651
|
|
|
8661
|
-
const S3_METADATA_LIMIT_BYTES$2 = 2e3;
|
|
8662
8652
|
async function handleInsert$2({ resource, data, mappedData }) {
|
|
8663
8653
|
const totalSize = calculateTotalSize(mappedData);
|
|
8664
|
-
if (totalSize > S3_METADATA_LIMIT_BYTES
|
|
8665
|
-
|
|
8654
|
+
if (totalSize > S3_METADATA_LIMIT_BYTES) {
|
|
8655
|
+
resource.emit("exceedsLimit", {
|
|
8656
|
+
operation: "insert",
|
|
8657
|
+
totalSize,
|
|
8658
|
+
limit: S3_METADATA_LIMIT_BYTES,
|
|
8659
|
+
excess: totalSize - S3_METADATA_LIMIT_BYTES,
|
|
8660
|
+
data
|
|
8661
|
+
});
|
|
8666
8662
|
}
|
|
8667
8663
|
return { mappedData, body: "" };
|
|
8668
8664
|
}
|
|
8669
8665
|
async function handleUpdate$2({ resource, id, data, mappedData }) {
|
|
8670
8666
|
const totalSize = calculateTotalSize(mappedData);
|
|
8671
|
-
if (totalSize > S3_METADATA_LIMIT_BYTES
|
|
8672
|
-
|
|
8667
|
+
if (totalSize > S3_METADATA_LIMIT_BYTES) {
|
|
8668
|
+
resource.emit("exceedsLimit", {
|
|
8669
|
+
operation: "update",
|
|
8670
|
+
id,
|
|
8671
|
+
totalSize,
|
|
8672
|
+
limit: S3_METADATA_LIMIT_BYTES,
|
|
8673
|
+
excess: totalSize - S3_METADATA_LIMIT_BYTES,
|
|
8674
|
+
data
|
|
8675
|
+
});
|
|
8673
8676
|
}
|
|
8674
8677
|
return { mappedData, body: "" };
|
|
8675
8678
|
}
|
|
8676
8679
|
async function handleUpsert$2({ resource, id, data, mappedData }) {
|
|
8677
8680
|
const totalSize = calculateTotalSize(mappedData);
|
|
8678
|
-
if (totalSize > S3_METADATA_LIMIT_BYTES
|
|
8679
|
-
|
|
8681
|
+
if (totalSize > S3_METADATA_LIMIT_BYTES) {
|
|
8682
|
+
resource.emit("exceedsLimit", {
|
|
8683
|
+
operation: "upsert",
|
|
8684
|
+
id,
|
|
8685
|
+
totalSize,
|
|
8686
|
+
limit: S3_METADATA_LIMIT_BYTES,
|
|
8687
|
+
excess: totalSize - S3_METADATA_LIMIT_BYTES,
|
|
8688
|
+
data
|
|
8689
|
+
});
|
|
8680
8690
|
}
|
|
8681
8691
|
return { mappedData, body: "" };
|
|
8682
8692
|
}
|
|
@@ -8684,7 +8694,7 @@ async function handleGet$2({ resource, metadata, body }) {
|
|
|
8684
8694
|
return { metadata, body };
|
|
8685
8695
|
}
|
|
8686
8696
|
|
|
8687
|
-
var
|
|
8697
|
+
var userManagement = /*#__PURE__*/Object.freeze({
|
|
8688
8698
|
__proto__: null,
|
|
8689
8699
|
handleGet: handleGet$2,
|
|
8690
8700
|
handleInsert: handleInsert$2,
|
|
@@ -8692,7 +8702,6 @@ var enforceLimits = /*#__PURE__*/Object.freeze({
|
|
|
8692
8702
|
handleUpsert: handleUpsert$2
|
|
8693
8703
|
});
|
|
8694
8704
|
|
|
8695
|
-
const S3_METADATA_LIMIT_BYTES$1 = 2e3;
|
|
8696
8705
|
const TRUNCATE_SUFFIX = "...";
|
|
8697
8706
|
const TRUNCATE_SUFFIX_BYTES = calculateUTF8Bytes(TRUNCATE_SUFFIX);
|
|
8698
8707
|
async function handleInsert$1({ resource, data, mappedData }) {
|
|
@@ -8713,7 +8722,7 @@ function handleTruncate({ resource, data, mappedData }) {
|
|
|
8713
8722
|
const result = {};
|
|
8714
8723
|
let currentSize = 0;
|
|
8715
8724
|
for (const [key, size] of sortedAttributes) {
|
|
8716
|
-
const availableSpace = S3_METADATA_LIMIT_BYTES
|
|
8725
|
+
const availableSpace = S3_METADATA_LIMIT_BYTES - currentSize;
|
|
8717
8726
|
if (size <= availableSpace) {
|
|
8718
8727
|
result[key] = mappedData[key];
|
|
8719
8728
|
currentSize += size;
|
|
@@ -8733,7 +8742,7 @@ function handleTruncate({ resource, data, mappedData }) {
|
|
|
8733
8742
|
}
|
|
8734
8743
|
}
|
|
8735
8744
|
result[key] = truncatedValue + TRUNCATE_SUFFIX;
|
|
8736
|
-
currentSize = S3_METADATA_LIMIT_BYTES
|
|
8745
|
+
currentSize = S3_METADATA_LIMIT_BYTES;
|
|
8737
8746
|
break;
|
|
8738
8747
|
} else {
|
|
8739
8748
|
break;
|
|
@@ -8750,7 +8759,6 @@ var dataTruncate = /*#__PURE__*/Object.freeze({
|
|
|
8750
8759
|
handleUpsert: handleUpsert$1
|
|
8751
8760
|
});
|
|
8752
8761
|
|
|
8753
|
-
const S3_METADATA_LIMIT_BYTES = 2e3;
|
|
8754
8762
|
const OVERFLOW_FLAG = "$overflow";
|
|
8755
8763
|
const OVERFLOW_FLAG_VALUE = "true";
|
|
8756
8764
|
const OVERFLOW_FLAG_BYTES = calculateUTF8Bytes(OVERFLOW_FLAG) + calculateUTF8Bytes(OVERFLOW_FLAG_VALUE);
|
|
@@ -9093,13 +9101,29 @@ class Resource extends EventEmitter {
|
|
|
9093
9101
|
return join(`resource=${this.name}`, `v=${this.version}`, `id=${id}`);
|
|
9094
9102
|
}
|
|
9095
9103
|
/**
|
|
9096
|
-
*
|
|
9097
|
-
* @param {
|
|
9098
|
-
* @param {string}
|
|
9099
|
-
* @param {
|
|
9100
|
-
* @
|
|
9104
|
+
* Generate partition key for a resource in a specific partition
|
|
9105
|
+
* @param {Object} params - Partition key parameters
|
|
9106
|
+
* @param {string} params.partitionName - Name of the partition
|
|
9107
|
+
* @param {string} params.id - Resource ID
|
|
9108
|
+
* @param {Object} params.data - Resource data for partition value extraction
|
|
9109
|
+
* @returns {string|null} The partition key path or null if required fields are missing
|
|
9110
|
+
* @example
|
|
9111
|
+
* const partitionKey = resource.getPartitionKey({
|
|
9112
|
+
* partitionName: 'byUtmSource',
|
|
9113
|
+
* id: 'user-123',
|
|
9114
|
+
* data: { utm: { source: 'google' } }
|
|
9115
|
+
* });
|
|
9116
|
+
* // Returns: 'resource=users/partition=byUtmSource/utm.source=google/id=user-123'
|
|
9117
|
+
*
|
|
9118
|
+
* // Returns null if required field is missing
|
|
9119
|
+
* const nullKey = resource.getPartitionKey({
|
|
9120
|
+
* partitionName: 'byUtmSource',
|
|
9121
|
+
* id: 'user-123',
|
|
9122
|
+
* data: { name: 'John' } // Missing utm.source
|
|
9123
|
+
* });
|
|
9124
|
+
* // Returns: null
|
|
9101
9125
|
*/
|
|
9102
|
-
getPartitionKey(partitionName, id, data) {
|
|
9126
|
+
getPartitionKey({ partitionName, id, data }) {
|
|
9103
9127
|
const partition = this.options.partitions[partitionName];
|
|
9104
9128
|
if (!partition) {
|
|
9105
9129
|
throw new Error(`Partition '${partitionName}' not found`);
|
|
@@ -9130,15 +9154,36 @@ class Resource extends EventEmitter {
|
|
|
9130
9154
|
return data[fieldPath];
|
|
9131
9155
|
}
|
|
9132
9156
|
const keys = fieldPath.split(".");
|
|
9133
|
-
let
|
|
9157
|
+
let currentLevel = data;
|
|
9134
9158
|
for (const key of keys) {
|
|
9135
|
-
if (
|
|
9159
|
+
if (!currentLevel || typeof currentLevel !== "object" || !(key in currentLevel)) {
|
|
9136
9160
|
return void 0;
|
|
9137
9161
|
}
|
|
9138
|
-
|
|
9162
|
+
currentLevel = currentLevel[key];
|
|
9139
9163
|
}
|
|
9140
|
-
return
|
|
9164
|
+
return currentLevel;
|
|
9141
9165
|
}
|
|
9166
|
+
/**
|
|
9167
|
+
* Insert a new resource object
|
|
9168
|
+
* @param {Object} params - Insert parameters
|
|
9169
|
+
* @param {string} [params.id] - Resource ID (auto-generated if not provided)
|
|
9170
|
+
* @param {...Object} params - Resource attributes (any additional properties)
|
|
9171
|
+
* @returns {Promise<Object>} The inserted resource object with all attributes and generated ID
|
|
9172
|
+
* @example
|
|
9173
|
+
* // Insert with auto-generated ID
|
|
9174
|
+
* const user = await resource.insert({
|
|
9175
|
+
* name: 'John Doe',
|
|
9176
|
+
* email: 'john@example.com',
|
|
9177
|
+
* age: 30
|
|
9178
|
+
* });
|
|
9179
|
+
*
|
|
9180
|
+
* // Insert with custom ID
|
|
9181
|
+
* const user = await resource.insert({
|
|
9182
|
+
* id: 'custom-id-123',
|
|
9183
|
+
* name: 'Jane Smith',
|
|
9184
|
+
* email: 'jane@example.com'
|
|
9185
|
+
* });
|
|
9186
|
+
*/
|
|
9142
9187
|
async insert({ id, ...attributes }) {
|
|
9143
9188
|
if (this.options.timestamps) {
|
|
9144
9189
|
attributes.createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -9177,39 +9222,68 @@ class Resource extends EventEmitter {
|
|
|
9177
9222
|
this.emit("insert", final);
|
|
9178
9223
|
return final;
|
|
9179
9224
|
}
|
|
9225
|
+
/**
|
|
9226
|
+
* Retrieve a resource object by ID
|
|
9227
|
+
* @param {string} id - Resource ID
|
|
9228
|
+
* @returns {Promise<Object>} The resource object with all attributes and metadata
|
|
9229
|
+
* @example
|
|
9230
|
+
* const user = await resource.get('user-123');
|
|
9231
|
+
* console.log(user.name); // 'John Doe'
|
|
9232
|
+
* console.log(user._lastModified); // Date object
|
|
9233
|
+
* console.log(user._hasContent); // boolean
|
|
9234
|
+
*/
|
|
9180
9235
|
async get(id) {
|
|
9181
9236
|
const key = this.getResourceKey(id);
|
|
9182
|
-
|
|
9183
|
-
|
|
9184
|
-
|
|
9185
|
-
|
|
9186
|
-
|
|
9187
|
-
|
|
9188
|
-
|
|
9189
|
-
|
|
9190
|
-
|
|
9191
|
-
|
|
9192
|
-
|
|
9193
|
-
|
|
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
|
+
}
|
|
9194
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
|
+
const enhancedError = new Error(`Failed to get resource with id '${id}': ${error.message}`);
|
|
9271
|
+
enhancedError.originalError = error;
|
|
9272
|
+
enhancedError.resourceId = id;
|
|
9273
|
+
enhancedError.resourceKey = key;
|
|
9274
|
+
throw enhancedError;
|
|
9195
9275
|
}
|
|
9196
|
-
const { metadata: processedMetadata } = await behaviorImpl.handleGet({
|
|
9197
|
-
resource: this,
|
|
9198
|
-
metadata,
|
|
9199
|
-
body
|
|
9200
|
-
});
|
|
9201
|
-
let data = processedMetadata;
|
|
9202
|
-
data.id = id;
|
|
9203
|
-
data._contentLength = request.ContentLength;
|
|
9204
|
-
data._lastModified = request.LastModified;
|
|
9205
|
-
data._hasContent = request.ContentLength > 0;
|
|
9206
|
-
data._mimeType = request.ContentType || null;
|
|
9207
|
-
if (request.VersionId) data._versionId = request.VersionId;
|
|
9208
|
-
if (request.Expiration) data._expiresAt = request.Expiration;
|
|
9209
|
-
data._definitionHash = this.getDefinitionHash();
|
|
9210
|
-
this.emit("get", data);
|
|
9211
|
-
return data;
|
|
9212
9276
|
}
|
|
9277
|
+
/**
|
|
9278
|
+
* Check if a resource exists by ID
|
|
9279
|
+
* @param {string} id - Resource ID
|
|
9280
|
+
* @returns {Promise<boolean>} True if resource exists, false otherwise
|
|
9281
|
+
* @example
|
|
9282
|
+
* const exists = await resource.exists('user-123');
|
|
9283
|
+
* if (exists) {
|
|
9284
|
+
* console.log('User exists');
|
|
9285
|
+
* }
|
|
9286
|
+
*/
|
|
9213
9287
|
async exists(id) {
|
|
9214
9288
|
try {
|
|
9215
9289
|
const key = this.getResourceKey(id);
|
|
@@ -9219,6 +9293,24 @@ class Resource extends EventEmitter {
|
|
|
9219
9293
|
return false;
|
|
9220
9294
|
}
|
|
9221
9295
|
}
|
|
9296
|
+
/**
|
|
9297
|
+
* Update an existing resource object
|
|
9298
|
+
* @param {string} id - Resource ID
|
|
9299
|
+
* @param {Object} attributes - Attributes to update (partial update supported)
|
|
9300
|
+
* @returns {Promise<Object>} The updated resource object with all attributes
|
|
9301
|
+
* @example
|
|
9302
|
+
* // Update specific fields
|
|
9303
|
+
* const updatedUser = await resource.update('user-123', {
|
|
9304
|
+
* name: 'John Updated',
|
|
9305
|
+
* age: 31
|
|
9306
|
+
* });
|
|
9307
|
+
*
|
|
9308
|
+
* // Update with timestamps (if enabled)
|
|
9309
|
+
* const updatedUser = await resource.update('user-123', {
|
|
9310
|
+
* email: 'newemail@example.com'
|
|
9311
|
+
* });
|
|
9312
|
+
* console.log(updatedUser.updatedAt); // ISO timestamp
|
|
9313
|
+
*/
|
|
9222
9314
|
async update(id, attributes) {
|
|
9223
9315
|
const live = await this.get(id);
|
|
9224
9316
|
if (this.options.timestamps) {
|
|
@@ -9275,6 +9367,14 @@ class Resource extends EventEmitter {
|
|
|
9275
9367
|
this.emit("update", preProcessedData, validated);
|
|
9276
9368
|
return validated;
|
|
9277
9369
|
}
|
|
9370
|
+
/**
|
|
9371
|
+
* Delete a resource object by ID
|
|
9372
|
+
* @param {string} id - Resource ID
|
|
9373
|
+
* @returns {Promise<Object>} S3 delete response
|
|
9374
|
+
* @example
|
|
9375
|
+
* await resource.delete('user-123');
|
|
9376
|
+
* console.log('User deleted successfully');
|
|
9377
|
+
*/
|
|
9278
9378
|
async delete(id) {
|
|
9279
9379
|
let objectData;
|
|
9280
9380
|
try {
|
|
@@ -9289,6 +9389,20 @@ class Resource extends EventEmitter {
|
|
|
9289
9389
|
this.emit("delete", id);
|
|
9290
9390
|
return response;
|
|
9291
9391
|
}
|
|
9392
|
+
/**
|
|
9393
|
+
* Insert or update a resource object (upsert operation)
|
|
9394
|
+
* @param {Object} params - Upsert parameters
|
|
9395
|
+
* @param {string} params.id - Resource ID (required for upsert)
|
|
9396
|
+
* @param {...Object} params - Resource attributes (any additional properties)
|
|
9397
|
+
* @returns {Promise<Object>} The inserted or updated resource object
|
|
9398
|
+
* @example
|
|
9399
|
+
* // Will insert if doesn't exist, update if exists
|
|
9400
|
+
* const user = await resource.upsert({
|
|
9401
|
+
* id: 'user-123',
|
|
9402
|
+
* name: 'John Doe',
|
|
9403
|
+
* email: 'john@example.com'
|
|
9404
|
+
* });
|
|
9405
|
+
*/
|
|
9292
9406
|
async upsert({ id, ...attributes }) {
|
|
9293
9407
|
const exists = await this.exists(id);
|
|
9294
9408
|
if (exists) {
|
|
@@ -9296,6 +9410,28 @@ class Resource extends EventEmitter {
|
|
|
9296
9410
|
}
|
|
9297
9411
|
return this.insert({ id, ...attributes });
|
|
9298
9412
|
}
|
|
9413
|
+
/**
|
|
9414
|
+
* Count resources with optional partition filtering
|
|
9415
|
+
* @param {Object} [params] - Count parameters
|
|
9416
|
+
* @param {string} [params.partition] - Partition name to count in
|
|
9417
|
+
* @param {Object} [params.partitionValues] - Partition field values to filter by
|
|
9418
|
+
* @returns {Promise<number>} Total count of matching resources
|
|
9419
|
+
* @example
|
|
9420
|
+
* // Count all resources
|
|
9421
|
+
* const total = await resource.count();
|
|
9422
|
+
*
|
|
9423
|
+
* // Count in specific partition
|
|
9424
|
+
* const googleUsers = await resource.count({
|
|
9425
|
+
* partition: 'byUtmSource',
|
|
9426
|
+
* partitionValues: { 'utm.source': 'google' }
|
|
9427
|
+
* });
|
|
9428
|
+
*
|
|
9429
|
+
* // Count in multi-field partition
|
|
9430
|
+
* const usElectronics = await resource.count({
|
|
9431
|
+
* partition: 'byCategoryRegion',
|
|
9432
|
+
* partitionValues: { category: 'electronics', region: 'US' }
|
|
9433
|
+
* });
|
|
9434
|
+
*/
|
|
9299
9435
|
async count({ partition = null, partitionValues = {} } = {}) {
|
|
9300
9436
|
let prefix;
|
|
9301
9437
|
if (partition && Object.keys(partitionValues).length > 0) {
|
|
@@ -9326,6 +9462,19 @@ class Resource extends EventEmitter {
|
|
|
9326
9462
|
this.emit("count", count);
|
|
9327
9463
|
return count;
|
|
9328
9464
|
}
|
|
9465
|
+
/**
|
|
9466
|
+
* Insert multiple resources in parallel
|
|
9467
|
+
* @param {Object[]} objects - Array of resource objects to insert
|
|
9468
|
+
* @returns {Promise<Object[]>} Array of inserted resource objects
|
|
9469
|
+
* @example
|
|
9470
|
+
* const users = [
|
|
9471
|
+
* { name: 'John', email: 'john@example.com' },
|
|
9472
|
+
* { name: 'Jane', email: 'jane@example.com' },
|
|
9473
|
+
* { name: 'Bob', email: 'bob@example.com' }
|
|
9474
|
+
* ];
|
|
9475
|
+
* const insertedUsers = await resource.insertMany(users);
|
|
9476
|
+
* console.log(`Inserted ${insertedUsers.length} users`);
|
|
9477
|
+
*/
|
|
9329
9478
|
async insertMany(objects) {
|
|
9330
9479
|
const { results } = await PromisePool.for(objects).withConcurrency(this.parallelism).handleError(async (error, content) => {
|
|
9331
9480
|
this.emit("error", error, content);
|
|
@@ -9337,6 +9486,15 @@ class Resource extends EventEmitter {
|
|
|
9337
9486
|
this.emit("insertMany", objects.length);
|
|
9338
9487
|
return results;
|
|
9339
9488
|
}
|
|
9489
|
+
/**
|
|
9490
|
+
* Delete multiple resources by their IDs in parallel
|
|
9491
|
+
* @param {string[]} ids - Array of resource IDs to delete
|
|
9492
|
+
* @returns {Promise<Object[]>} Array of S3 delete responses
|
|
9493
|
+
* @example
|
|
9494
|
+
* const deletedIds = ['user-1', 'user-2', 'user-3'];
|
|
9495
|
+
* const results = await resource.deleteMany(deletedIds);
|
|
9496
|
+
* console.log(`Deleted ${deletedIds.length} users`);
|
|
9497
|
+
*/
|
|
9340
9498
|
async deleteMany(ids) {
|
|
9341
9499
|
const packages = chunk(
|
|
9342
9500
|
ids.map((id) => this.getResourceKey(id)),
|
|
@@ -9395,7 +9553,35 @@ class Resource extends EventEmitter {
|
|
|
9395
9553
|
});
|
|
9396
9554
|
return { deletedCount, resource: this.name };
|
|
9397
9555
|
}
|
|
9398
|
-
|
|
9556
|
+
/**
|
|
9557
|
+
* List resource IDs with optional partition filtering and pagination
|
|
9558
|
+
* @param {Object} [params] - List parameters
|
|
9559
|
+
* @param {string} [params.partition] - Partition name to list from
|
|
9560
|
+
* @param {Object} [params.partitionValues] - Partition field values to filter by
|
|
9561
|
+
* @param {number} [params.limit] - Maximum number of results to return
|
|
9562
|
+
* @param {number} [params.offset=0] - Offset for pagination
|
|
9563
|
+
* @returns {Promise<string[]>} Array of resource IDs (strings)
|
|
9564
|
+
* @example
|
|
9565
|
+
* // List all IDs
|
|
9566
|
+
* const allIds = await resource.listIds();
|
|
9567
|
+
*
|
|
9568
|
+
* // List IDs with pagination
|
|
9569
|
+
* const firstPageIds = await resource.listIds({ limit: 10, offset: 0 });
|
|
9570
|
+
* const secondPageIds = await resource.listIds({ limit: 10, offset: 10 });
|
|
9571
|
+
*
|
|
9572
|
+
* // List IDs from specific partition
|
|
9573
|
+
* const googleUserIds = await resource.listIds({
|
|
9574
|
+
* partition: 'byUtmSource',
|
|
9575
|
+
* partitionValues: { 'utm.source': 'google' }
|
|
9576
|
+
* });
|
|
9577
|
+
*
|
|
9578
|
+
* // List IDs from multi-field partition
|
|
9579
|
+
* const usElectronicsIds = await resource.listIds({
|
|
9580
|
+
* partition: 'byCategoryRegion',
|
|
9581
|
+
* partitionValues: { category: 'electronics', region: 'US' }
|
|
9582
|
+
* });
|
|
9583
|
+
*/
|
|
9584
|
+
async listIds({ partition = null, partitionValues = {}, limit, offset = 0 } = {}) {
|
|
9399
9585
|
let prefix;
|
|
9400
9586
|
if (partition && Object.keys(partitionValues).length > 0) {
|
|
9401
9587
|
const partitionDef = this.options.partitions[partition];
|
|
@@ -9419,8 +9605,11 @@ class Resource extends EventEmitter {
|
|
|
9419
9605
|
} else {
|
|
9420
9606
|
prefix = `resource=${this.name}/v=${this.version}`;
|
|
9421
9607
|
}
|
|
9422
|
-
const keys = await this.client.
|
|
9423
|
-
prefix
|
|
9608
|
+
const keys = await this.client.getKeysPage({
|
|
9609
|
+
prefix,
|
|
9610
|
+
offset,
|
|
9611
|
+
amount: limit || 1e3
|
|
9612
|
+
// Default to 1000 if no limit specified
|
|
9424
9613
|
});
|
|
9425
9614
|
const ids = keys.map((key) => {
|
|
9426
9615
|
const parts = key.split("/");
|
|
@@ -9431,13 +9620,36 @@ class Resource extends EventEmitter {
|
|
|
9431
9620
|
return ids;
|
|
9432
9621
|
}
|
|
9433
9622
|
/**
|
|
9434
|
-
* List objects
|
|
9435
|
-
* @param {Object}
|
|
9436
|
-
* @param {
|
|
9437
|
-
* @
|
|
9623
|
+
* List resource objects with optional partition filtering and pagination
|
|
9624
|
+
* @param {Object} [params] - List parameters
|
|
9625
|
+
* @param {string} [params.partition] - Partition name to list from
|
|
9626
|
+
* @param {Object} [params.partitionValues] - Partition field values to filter by
|
|
9627
|
+
* @param {number} [params.limit] - Maximum number of results to return
|
|
9628
|
+
* @param {number} [params.offset=0] - Offset for pagination
|
|
9629
|
+
* @returns {Promise<Object[]>} Array of resource objects with all attributes
|
|
9630
|
+
* @example
|
|
9631
|
+
* // List all resources
|
|
9632
|
+
* const allUsers = await resource.list();
|
|
9633
|
+
*
|
|
9634
|
+
* // List with pagination
|
|
9635
|
+
* const firstPage = await resource.list({ limit: 10, offset: 0 });
|
|
9636
|
+
* const secondPage = await resource.list({ limit: 10, offset: 10 });
|
|
9637
|
+
*
|
|
9638
|
+
* // List from specific partition
|
|
9639
|
+
* const googleUsers = await resource.list({
|
|
9640
|
+
* partition: 'byUtmSource',
|
|
9641
|
+
* partitionValues: { 'utm.source': 'google' }
|
|
9642
|
+
* });
|
|
9643
|
+
*
|
|
9644
|
+
* // List from partition with pagination
|
|
9645
|
+
* const googleUsersPage = await resource.list({
|
|
9646
|
+
* partition: 'byUtmSource',
|
|
9647
|
+
* partitionValues: { 'utm.source': 'google' },
|
|
9648
|
+
* limit: 5,
|
|
9649
|
+
* offset: 0
|
|
9650
|
+
* });
|
|
9438
9651
|
*/
|
|
9439
|
-
async
|
|
9440
|
-
const { limit, offset = 0 } = options;
|
|
9652
|
+
async list({ partition = null, partitionValues = {}, limit, offset = 0 } = {}) {
|
|
9441
9653
|
if (!partition) {
|
|
9442
9654
|
const ids2 = await this.listIds({ partition, partitionValues });
|
|
9443
9655
|
let filteredIds2 = ids2.slice(offset);
|
|
@@ -9447,7 +9659,7 @@ class Resource extends EventEmitter {
|
|
|
9447
9659
|
const { results: results2 } = await PromisePool.for(filteredIds2).withConcurrency(this.parallelism).process(async (id) => {
|
|
9448
9660
|
return await this.get(id);
|
|
9449
9661
|
});
|
|
9450
|
-
this.emit("
|
|
9662
|
+
this.emit("list", { partition, partitionValues, count: results2.length });
|
|
9451
9663
|
return results2;
|
|
9452
9664
|
}
|
|
9453
9665
|
const partitionDef = this.options.partitions[partition];
|
|
@@ -9480,11 +9692,19 @@ class Resource extends EventEmitter {
|
|
|
9480
9692
|
filteredIds = filteredIds.slice(0, limit);
|
|
9481
9693
|
}
|
|
9482
9694
|
const { results } = await PromisePool.for(filteredIds).withConcurrency(this.parallelism).process(async (id) => {
|
|
9483
|
-
return await this.getFromPartition(id, partition, partitionValues);
|
|
9695
|
+
return await this.getFromPartition({ id, partitionName: partition, partitionValues });
|
|
9484
9696
|
});
|
|
9485
|
-
this.emit("
|
|
9697
|
+
this.emit("list", { partition, partitionValues, count: results.length });
|
|
9486
9698
|
return results;
|
|
9487
9699
|
}
|
|
9700
|
+
/**
|
|
9701
|
+
* Get multiple resources by their IDs
|
|
9702
|
+
* @param {string[]} ids - Array of resource IDs
|
|
9703
|
+
* @returns {Promise<Object[]>} Array of resource objects
|
|
9704
|
+
* @example
|
|
9705
|
+
* const users = await resource.getMany(['user-1', 'user-2', 'user-3']);
|
|
9706
|
+
* users.forEach(user => console.log(user.name));
|
|
9707
|
+
*/
|
|
9488
9708
|
async getMany(ids) {
|
|
9489
9709
|
const { results } = await PromisePool.for(ids).withConcurrency(this.client.parallelism).process(async (id) => {
|
|
9490
9710
|
this.emit("id", id);
|
|
@@ -9495,6 +9715,13 @@ class Resource extends EventEmitter {
|
|
|
9495
9715
|
this.emit("getMany", ids.length);
|
|
9496
9716
|
return results;
|
|
9497
9717
|
}
|
|
9718
|
+
/**
|
|
9719
|
+
* Get all resources (equivalent to list() without pagination)
|
|
9720
|
+
* @returns {Promise<Object[]>} Array of all resource objects
|
|
9721
|
+
* @example
|
|
9722
|
+
* const allUsers = await resource.getAll();
|
|
9723
|
+
* console.log(`Total users: ${allUsers.length}`);
|
|
9724
|
+
*/
|
|
9498
9725
|
async getAll() {
|
|
9499
9726
|
let ids = await this.listIds();
|
|
9500
9727
|
if (ids.length === 0) return [];
|
|
@@ -9505,20 +9732,65 @@ class Resource extends EventEmitter {
|
|
|
9505
9732
|
this.emit("getAll", results.length);
|
|
9506
9733
|
return results;
|
|
9507
9734
|
}
|
|
9508
|
-
|
|
9509
|
-
|
|
9510
|
-
|
|
9511
|
-
|
|
9512
|
-
|
|
9513
|
-
|
|
9514
|
-
|
|
9515
|
-
|
|
9735
|
+
/**
|
|
9736
|
+
* Get a page of resources with pagination metadata
|
|
9737
|
+
* @param {Object} [params] - Page parameters
|
|
9738
|
+
* @param {number} [params.offset=0] - Offset for pagination
|
|
9739
|
+
* @param {number} [params.size=100] - Page size
|
|
9740
|
+
* @param {string} [params.partition] - Partition name to page from
|
|
9741
|
+
* @param {Object} [params.partitionValues] - Partition field values to filter by
|
|
9742
|
+
* @param {boolean} [params.skipCount=false] - Skip total count for performance (useful for large collections)
|
|
9743
|
+
* @returns {Promise<Object>} Page result with items and pagination info
|
|
9744
|
+
* @example
|
|
9745
|
+
* // Get first page of all resources
|
|
9746
|
+
* const page = await resource.page({ offset: 0, size: 10 });
|
|
9747
|
+
* console.log(`Page ${page.page + 1} of ${page.totalPages}`);
|
|
9748
|
+
* console.log(`Showing ${page.items.length} of ${page.totalItems} total`);
|
|
9749
|
+
*
|
|
9750
|
+
* // Get page from specific partition
|
|
9751
|
+
* const googlePage = await resource.page({
|
|
9752
|
+
* partition: 'byUtmSource',
|
|
9753
|
+
* partitionValues: { 'utm.source': 'google' },
|
|
9754
|
+
* offset: 0,
|
|
9755
|
+
* size: 5
|
|
9756
|
+
* });
|
|
9757
|
+
*
|
|
9758
|
+
* // Skip count for performance in large collections
|
|
9759
|
+
* const fastPage = await resource.page({
|
|
9760
|
+
* offset: 0,
|
|
9761
|
+
* size: 100,
|
|
9762
|
+
* skipCount: true
|
|
9763
|
+
* });
|
|
9764
|
+
* console.log(`Got ${fastPage.items.length} items`); // totalItems will be null
|
|
9765
|
+
*/
|
|
9766
|
+
async page({ offset = 0, size = 100, partition = null, partitionValues = {}, skipCount = false } = {}) {
|
|
9767
|
+
let totalItems = null;
|
|
9768
|
+
let totalPages = null;
|
|
9769
|
+
if (!skipCount) {
|
|
9770
|
+
totalItems = await this.count({ partition, partitionValues });
|
|
9771
|
+
totalPages = Math.ceil(totalItems / size);
|
|
9772
|
+
}
|
|
9773
|
+
const page = Math.floor(offset / size);
|
|
9774
|
+
const items = await this.list({
|
|
9775
|
+
partition,
|
|
9776
|
+
partitionValues,
|
|
9777
|
+
limit: size,
|
|
9778
|
+
offset
|
|
9779
|
+
});
|
|
9516
9780
|
const result = {
|
|
9517
9781
|
items,
|
|
9518
9782
|
totalItems,
|
|
9519
|
-
page
|
|
9783
|
+
page,
|
|
9520
9784
|
pageSize: size,
|
|
9521
|
-
totalPages
|
|
9785
|
+
totalPages,
|
|
9786
|
+
// Add additional metadata for debugging
|
|
9787
|
+
_debug: {
|
|
9788
|
+
requestedSize: size,
|
|
9789
|
+
requestedOffset: offset,
|
|
9790
|
+
actualItemsReturned: items.length,
|
|
9791
|
+
skipCount,
|
|
9792
|
+
hasTotalItems: totalItems !== null
|
|
9793
|
+
}
|
|
9522
9794
|
};
|
|
9523
9795
|
this.emit("page", result);
|
|
9524
9796
|
return result;
|
|
@@ -9532,36 +9804,62 @@ class Resource extends EventEmitter {
|
|
|
9532
9804
|
return stream.build();
|
|
9533
9805
|
}
|
|
9534
9806
|
/**
|
|
9535
|
-
*
|
|
9536
|
-
* @param {
|
|
9537
|
-
* @param {
|
|
9538
|
-
* @param {string}
|
|
9807
|
+
* Set binary content for a resource
|
|
9808
|
+
* @param {Object} params - Content parameters
|
|
9809
|
+
* @param {string} params.id - Resource ID
|
|
9810
|
+
* @param {Buffer|string} params.buffer - Content buffer or string
|
|
9811
|
+
* @param {string} [params.contentType='application/octet-stream'] - Content type
|
|
9812
|
+
* @returns {Promise<Object>} Updated resource data
|
|
9813
|
+
* @example
|
|
9814
|
+
* // Set image content
|
|
9815
|
+
* const imageBuffer = fs.readFileSync('image.jpg');
|
|
9816
|
+
* await resource.setContent({
|
|
9817
|
+
* id: 'user-123',
|
|
9818
|
+
* buffer: imageBuffer,
|
|
9819
|
+
* contentType: 'image/jpeg'
|
|
9820
|
+
* });
|
|
9821
|
+
*
|
|
9822
|
+
* // Set text content
|
|
9823
|
+
* await resource.setContent({
|
|
9824
|
+
* id: 'document-456',
|
|
9825
|
+
* buffer: 'Hello World',
|
|
9826
|
+
* contentType: 'text/plain'
|
|
9827
|
+
* });
|
|
9539
9828
|
*/
|
|
9540
|
-
async setContent(id, buffer, contentType = "application/octet-stream") {
|
|
9541
|
-
|
|
9542
|
-
|
|
9543
|
-
|
|
9544
|
-
|
|
9545
|
-
|
|
9546
|
-
|
|
9547
|
-
|
|
9548
|
-
|
|
9549
|
-
|
|
9550
|
-
}
|
|
9551
|
-
|
|
9552
|
-
key,
|
|
9829
|
+
async setContent({ id, buffer, contentType = "application/octet-stream" }) {
|
|
9830
|
+
const currentData = await this.get(id);
|
|
9831
|
+
if (!currentData) {
|
|
9832
|
+
throw new Error(`Resource with id '${id}' not found`);
|
|
9833
|
+
}
|
|
9834
|
+
const updatedData = {
|
|
9835
|
+
...currentData,
|
|
9836
|
+
_hasContent: true,
|
|
9837
|
+
_contentLength: buffer.length,
|
|
9838
|
+
_mimeType: contentType
|
|
9839
|
+
};
|
|
9840
|
+
await this.client.putObject({
|
|
9841
|
+
key: this.getResourceKey(id),
|
|
9842
|
+
metadata: await this.schema.mapper(updatedData),
|
|
9553
9843
|
body: buffer,
|
|
9554
|
-
contentType
|
|
9555
|
-
metadata: existingMetadata
|
|
9556
|
-
// Preserve existing metadata
|
|
9844
|
+
contentType
|
|
9557
9845
|
});
|
|
9558
|
-
this.emit("setContent", id, buffer.length
|
|
9559
|
-
return
|
|
9846
|
+
this.emit("setContent", { id, contentType, contentLength: buffer.length });
|
|
9847
|
+
return updatedData;
|
|
9560
9848
|
}
|
|
9561
9849
|
/**
|
|
9562
9850
|
* Retrieve binary content associated with a resource
|
|
9563
9851
|
* @param {string} id - Resource ID
|
|
9564
|
-
* @returns {Object} Object with buffer and contentType
|
|
9852
|
+
* @returns {Promise<Object>} Object with buffer and contentType
|
|
9853
|
+
* @example
|
|
9854
|
+
* const content = await resource.content('user-123');
|
|
9855
|
+
* if (content.buffer) {
|
|
9856
|
+
* console.log('Content type:', content.contentType);
|
|
9857
|
+
* console.log('Content size:', content.buffer.length);
|
|
9858
|
+
* // Save to file
|
|
9859
|
+
* fs.writeFileSync('output.jpg', content.buffer);
|
|
9860
|
+
* } else {
|
|
9861
|
+
* console.log('No content found');
|
|
9862
|
+
* }
|
|
9565
9863
|
*/
|
|
9566
9864
|
async content(id) {
|
|
9567
9865
|
const key = this.getResourceKey(id);
|
|
@@ -9656,7 +9954,7 @@ class Resource extends EventEmitter {
|
|
|
9656
9954
|
return;
|
|
9657
9955
|
}
|
|
9658
9956
|
for (const [partitionName, partition] of Object.entries(partitions)) {
|
|
9659
|
-
const partitionKey = this.getPartitionKey(partitionName, data.id, data);
|
|
9957
|
+
const partitionKey = this.getPartitionKey({ partitionName, id: data.id, data });
|
|
9660
9958
|
if (partitionKey) {
|
|
9661
9959
|
const mappedData = await this.schema.mapper(data);
|
|
9662
9960
|
const behaviorImpl = getBehavior(this.behavior);
|
|
@@ -9688,7 +9986,7 @@ class Resource extends EventEmitter {
|
|
|
9688
9986
|
}
|
|
9689
9987
|
const keysToDelete = [];
|
|
9690
9988
|
for (const [partitionName, partition] of Object.entries(partitions)) {
|
|
9691
|
-
const partitionKey = this.getPartitionKey(partitionName, data.id, data);
|
|
9989
|
+
const partitionKey = this.getPartitionKey({ partitionName, id: data.id, data });
|
|
9692
9990
|
if (partitionKey) {
|
|
9693
9991
|
keysToDelete.push(partitionKey);
|
|
9694
9992
|
}
|
|
@@ -9702,20 +10000,72 @@ class Resource extends EventEmitter {
|
|
|
9702
10000
|
}
|
|
9703
10001
|
}
|
|
9704
10002
|
/**
|
|
9705
|
-
* Query
|
|
9706
|
-
* @param {Object} filter - Filter criteria
|
|
9707
|
-
* @
|
|
10003
|
+
* Query resources with simple filtering and pagination
|
|
10004
|
+
* @param {Object} [filter={}] - Filter criteria (exact field matches)
|
|
10005
|
+
* @param {Object} [options] - Query options
|
|
10006
|
+
* @param {number} [options.limit=100] - Maximum number of results
|
|
10007
|
+
* @param {number} [options.offset=0] - Offset for pagination
|
|
10008
|
+
* @param {string} [options.partition] - Partition name to query from
|
|
10009
|
+
* @param {Object} [options.partitionValues] - Partition field values to filter by
|
|
10010
|
+
* @returns {Promise<Object[]>} Array of filtered resource objects
|
|
10011
|
+
* @example
|
|
10012
|
+
* // Query all resources (no filter)
|
|
10013
|
+
* const allUsers = await resource.query();
|
|
10014
|
+
*
|
|
10015
|
+
* // Query with simple filter
|
|
10016
|
+
* const activeUsers = await resource.query({ status: 'active' });
|
|
10017
|
+
*
|
|
10018
|
+
* // Query with multiple filters
|
|
10019
|
+
* const usElectronics = await resource.query({
|
|
10020
|
+
* category: 'electronics',
|
|
10021
|
+
* region: 'US'
|
|
10022
|
+
* });
|
|
10023
|
+
*
|
|
10024
|
+
* // Query with pagination
|
|
10025
|
+
* const firstPage = await resource.query(
|
|
10026
|
+
* { status: 'active' },
|
|
10027
|
+
* { limit: 10, offset: 0 }
|
|
10028
|
+
* );
|
|
10029
|
+
*
|
|
10030
|
+
* // Query within partition
|
|
10031
|
+
* const googleUsers = await resource.query(
|
|
10032
|
+
* { status: 'active' },
|
|
10033
|
+
* {
|
|
10034
|
+
* partition: 'byUtmSource',
|
|
10035
|
+
* partitionValues: { 'utm.source': 'google' },
|
|
10036
|
+
* limit: 5
|
|
10037
|
+
* }
|
|
10038
|
+
* );
|
|
9708
10039
|
*/
|
|
9709
|
-
async query(filter = {}) {
|
|
9710
|
-
const allDocuments = await this.getAll();
|
|
10040
|
+
async query(filter = {}, { limit = 100, offset = 0, partition = null, partitionValues = {} } = {}) {
|
|
9711
10041
|
if (Object.keys(filter).length === 0) {
|
|
9712
|
-
return
|
|
9713
|
-
}
|
|
9714
|
-
|
|
9715
|
-
|
|
9716
|
-
|
|
10042
|
+
return await this.list({ partition, partitionValues, limit, offset });
|
|
10043
|
+
}
|
|
10044
|
+
const results = [];
|
|
10045
|
+
let currentOffset = offset;
|
|
10046
|
+
const batchSize = Math.min(limit, 50);
|
|
10047
|
+
while (results.length < limit) {
|
|
10048
|
+
const batch = await this.list({
|
|
10049
|
+
partition,
|
|
10050
|
+
partitionValues,
|
|
10051
|
+
limit: batchSize,
|
|
10052
|
+
offset: currentOffset
|
|
9717
10053
|
});
|
|
9718
|
-
|
|
10054
|
+
if (batch.length === 0) {
|
|
10055
|
+
break;
|
|
10056
|
+
}
|
|
10057
|
+
const filteredBatch = batch.filter((doc) => {
|
|
10058
|
+
return Object.entries(filter).every(([key, value]) => {
|
|
10059
|
+
return doc[key] === value;
|
|
10060
|
+
});
|
|
10061
|
+
});
|
|
10062
|
+
results.push(...filteredBatch);
|
|
10063
|
+
currentOffset += batchSize;
|
|
10064
|
+
if (batch.length < batchSize) {
|
|
10065
|
+
break;
|
|
10066
|
+
}
|
|
10067
|
+
}
|
|
10068
|
+
return results.slice(0, limit);
|
|
9719
10069
|
}
|
|
9720
10070
|
/**
|
|
9721
10071
|
* Update partition objects to keep them in sync
|
|
@@ -9727,7 +10077,7 @@ class Resource extends EventEmitter {
|
|
|
9727
10077
|
return;
|
|
9728
10078
|
}
|
|
9729
10079
|
for (const [partitionName, partition] of Object.entries(partitions)) {
|
|
9730
|
-
const partitionKey = this.getPartitionKey(partitionName, data.id, data);
|
|
10080
|
+
const partitionKey = this.getPartitionKey({ partitionName, id: data.id, data });
|
|
9731
10081
|
if (partitionKey) {
|
|
9732
10082
|
const mappedData = await this.schema.mapper(data);
|
|
9733
10083
|
const behaviorImpl = getBehavior(this.behavior);
|
|
@@ -9754,13 +10104,30 @@ class Resource extends EventEmitter {
|
|
|
9754
10104
|
}
|
|
9755
10105
|
}
|
|
9756
10106
|
/**
|
|
9757
|
-
* Get object directly from a specific partition
|
|
9758
|
-
* @param {
|
|
9759
|
-
* @param {string}
|
|
9760
|
-
* @param {
|
|
9761
|
-
* @
|
|
10107
|
+
* Get a resource object directly from a specific partition
|
|
10108
|
+
* @param {Object} params - Partition parameters
|
|
10109
|
+
* @param {string} params.id - Resource ID
|
|
10110
|
+
* @param {string} params.partitionName - Name of the partition
|
|
10111
|
+
* @param {Object} params.partitionValues - Values for partition fields
|
|
10112
|
+
* @returns {Promise<Object>} The resource object with partition metadata
|
|
10113
|
+
* @example
|
|
10114
|
+
* // Get user from UTM source partition
|
|
10115
|
+
* const user = await resource.getFromPartition({
|
|
10116
|
+
* id: 'user-123',
|
|
10117
|
+
* partitionName: 'byUtmSource',
|
|
10118
|
+
* partitionValues: { 'utm.source': 'google' }
|
|
10119
|
+
* });
|
|
10120
|
+
* console.log(user._partition); // 'byUtmSource'
|
|
10121
|
+
* console.log(user._partitionValues); // { 'utm.source': 'google' }
|
|
10122
|
+
*
|
|
10123
|
+
* // Get product from multi-field partition
|
|
10124
|
+
* const product = await resource.getFromPartition({
|
|
10125
|
+
* id: 'product-456',
|
|
10126
|
+
* partitionName: 'byCategoryRegion',
|
|
10127
|
+
* partitionValues: { category: 'electronics', region: 'US' }
|
|
10128
|
+
* });
|
|
9762
10129
|
*/
|
|
9763
|
-
async getFromPartition(id, partitionName, partitionValues = {}) {
|
|
10130
|
+
async getFromPartition({ id, partitionName, partitionValues = {} }) {
|
|
9764
10131
|
const partition = this.options.partitions[partitionName];
|
|
9765
10132
|
if (!partition) {
|
|
9766
10133
|
throw new Error(`Partition '${partitionName}' not found`);
|
|
@@ -9819,7 +10186,7 @@ class Database extends EventEmitter {
|
|
|
9819
10186
|
this.version = "1";
|
|
9820
10187
|
this.s3dbVersion = (() => {
|
|
9821
10188
|
try {
|
|
9822
|
-
return true ? "4.1.
|
|
10189
|
+
return true ? "4.1.7" : "latest";
|
|
9823
10190
|
} catch (e) {
|
|
9824
10191
|
return "latest";
|
|
9825
10192
|
}
|