s3db.js 12.0.0 → 12.0.1
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/dist/s3db.cjs.js +313 -62
- package/dist/s3db.cjs.js.map +1 -1
- package/dist/s3db.d.ts +9 -1
- package/dist/s3db.es.js +312 -62
- package/dist/s3db.es.js.map +1 -1
- package/package.json +14 -15
- package/src/client.class.js +40 -5
- package/src/concerns/plugin-storage.js +67 -37
- package/src/plugins/api/index.js +5 -0
- package/src/plugins/api/server.js +4 -0
- package/src/plugins/replicator.plugin.js +2 -1
- package/src/resource.class.js +309 -34
- package/src/s3db.d.ts +9 -1
package/dist/s3db.d.ts
CHANGED
|
@@ -723,6 +723,8 @@ declare module 's3db.js' {
|
|
|
723
723
|
get(id: string): Promise<any>;
|
|
724
724
|
exists(id: string): Promise<boolean>;
|
|
725
725
|
update(id: string, attributes: any): Promise<any>;
|
|
726
|
+
patch(id: string, fields: any, options?: { partition?: string; partitionValues?: Record<string, any> }): Promise<any>;
|
|
727
|
+
replace(id: string, fullData: any, options?: { partition?: string; partitionValues?: Record<string, any> }): Promise<any>;
|
|
726
728
|
upsert(data: any): Promise<any>;
|
|
727
729
|
delete(id: string): Promise<void>;
|
|
728
730
|
deleteMany(ids: string[]): Promise<void>;
|
|
@@ -841,7 +843,13 @@ declare module 's3db.js' {
|
|
|
841
843
|
}): Promise<any>;
|
|
842
844
|
getObject(key: string): Promise<any>;
|
|
843
845
|
headObject(key: string): Promise<any>;
|
|
844
|
-
copyObject(options: {
|
|
846
|
+
copyObject(options: {
|
|
847
|
+
from: string;
|
|
848
|
+
to: string;
|
|
849
|
+
metadata?: Record<string, any>;
|
|
850
|
+
metadataDirective?: 'COPY' | 'REPLACE';
|
|
851
|
+
contentType?: string;
|
|
852
|
+
}): Promise<any>;
|
|
845
853
|
exists(key: string): Promise<boolean>;
|
|
846
854
|
deleteObject(key: string): Promise<any>;
|
|
847
855
|
deleteObjects(keys: string[]): Promise<{ deleted: any[]; notFound: any[] }>;
|
package/dist/s3db.es.js
CHANGED
|
@@ -27,7 +27,7 @@ import { realpath, readlink, readdir as readdir$1, lstat } from 'node:fs/promise
|
|
|
27
27
|
import { EventEmitter as EventEmitter$1 } from 'node:events';
|
|
28
28
|
import Stream from 'node:stream';
|
|
29
29
|
import { StringDecoder } from 'node:string_decoder';
|
|
30
|
-
import
|
|
30
|
+
import { createRequire } from 'node:module';
|
|
31
31
|
import require$$1 from 'child_process';
|
|
32
32
|
import require$$5 from 'url';
|
|
33
33
|
|
|
@@ -1963,44 +1963,31 @@ class PluginStorage {
|
|
|
1963
1963
|
* @returns {Promise<boolean>} True if extended, false if not found or no TTL
|
|
1964
1964
|
*/
|
|
1965
1965
|
async touch(key, additionalSeconds) {
|
|
1966
|
-
const [ok, err, response] = await tryFn(() => this.client.
|
|
1966
|
+
const [ok, err, response] = await tryFn(() => this.client.headObject(key));
|
|
1967
1967
|
if (!ok) {
|
|
1968
1968
|
return false;
|
|
1969
1969
|
}
|
|
1970
1970
|
const metadata = response.Metadata || {};
|
|
1971
1971
|
const parsedMetadata = this._parseMetadataValues(metadata);
|
|
1972
|
-
|
|
1973
|
-
if (response.Body) {
|
|
1974
|
-
const [ok2, err2, result] = await tryFn(async () => {
|
|
1975
|
-
const bodyContent = await response.Body.transformToString();
|
|
1976
|
-
if (bodyContent && bodyContent.trim()) {
|
|
1977
|
-
const body = JSON.parse(bodyContent);
|
|
1978
|
-
return { ...parsedMetadata, ...body };
|
|
1979
|
-
}
|
|
1980
|
-
return parsedMetadata;
|
|
1981
|
-
});
|
|
1982
|
-
if (!ok2) {
|
|
1983
|
-
return false;
|
|
1984
|
-
}
|
|
1985
|
-
data = result;
|
|
1986
|
-
}
|
|
1987
|
-
const expiresAt = data._expiresat || data._expiresAt;
|
|
1972
|
+
const expiresAt = parsedMetadata._expiresat || parsedMetadata._expiresAt;
|
|
1988
1973
|
if (!expiresAt) {
|
|
1989
1974
|
return false;
|
|
1990
1975
|
}
|
|
1991
|
-
|
|
1992
|
-
delete
|
|
1993
|
-
const
|
|
1994
|
-
const
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
1976
|
+
parsedMetadata._expiresAt = expiresAt + additionalSeconds * 1e3;
|
|
1977
|
+
delete parsedMetadata._expiresat;
|
|
1978
|
+
const encodedMetadata = {};
|
|
1979
|
+
for (const [metaKey, metaValue] of Object.entries(parsedMetadata)) {
|
|
1980
|
+
const { encoded } = metadataEncode(metaValue);
|
|
1981
|
+
encodedMetadata[metaKey] = encoded;
|
|
1982
|
+
}
|
|
1983
|
+
const [copyOk] = await tryFn(() => this.client.copyObject({
|
|
1984
|
+
from: key,
|
|
1985
|
+
to: key,
|
|
1986
|
+
metadata: encodedMetadata,
|
|
1987
|
+
metadataDirective: "REPLACE",
|
|
1988
|
+
contentType: response.ContentType || "application/json"
|
|
1989
|
+
}));
|
|
1990
|
+
return copyOk;
|
|
2004
1991
|
}
|
|
2005
1992
|
/**
|
|
2006
1993
|
* Delete a single object
|
|
@@ -2135,12 +2122,41 @@ class PluginStorage {
|
|
|
2135
2122
|
/**
|
|
2136
2123
|
* Increment a counter value
|
|
2137
2124
|
*
|
|
2125
|
+
* Optimization: Uses HEAD + COPY for existing counters to avoid body transfer.
|
|
2126
|
+
* Falls back to GET + PUT for non-existent counters or those with additional data.
|
|
2127
|
+
*
|
|
2138
2128
|
* @param {string} key - S3 key
|
|
2139
2129
|
* @param {number} amount - Amount to increment (default: 1)
|
|
2140
2130
|
* @param {Object} options - Options (e.g., ttl)
|
|
2141
2131
|
* @returns {Promise<number>} New value
|
|
2142
2132
|
*/
|
|
2143
2133
|
async increment(key, amount = 1, options = {}) {
|
|
2134
|
+
const [headOk, headErr, headResponse] = await tryFn(() => this.client.headObject(key));
|
|
2135
|
+
if (headOk && headResponse.Metadata) {
|
|
2136
|
+
const metadata = headResponse.Metadata || {};
|
|
2137
|
+
const parsedMetadata = this._parseMetadataValues(metadata);
|
|
2138
|
+
const currentValue = parsedMetadata.value || 0;
|
|
2139
|
+
const newValue = currentValue + amount;
|
|
2140
|
+
parsedMetadata.value = newValue;
|
|
2141
|
+
if (options.ttl) {
|
|
2142
|
+
parsedMetadata._expiresAt = Date.now() + options.ttl * 1e3;
|
|
2143
|
+
}
|
|
2144
|
+
const encodedMetadata = {};
|
|
2145
|
+
for (const [metaKey, metaValue] of Object.entries(parsedMetadata)) {
|
|
2146
|
+
const { encoded } = metadataEncode(metaValue);
|
|
2147
|
+
encodedMetadata[metaKey] = encoded;
|
|
2148
|
+
}
|
|
2149
|
+
const [copyOk] = await tryFn(() => this.client.copyObject({
|
|
2150
|
+
from: key,
|
|
2151
|
+
to: key,
|
|
2152
|
+
metadata: encodedMetadata,
|
|
2153
|
+
metadataDirective: "REPLACE",
|
|
2154
|
+
contentType: headResponse.ContentType || "application/json"
|
|
2155
|
+
}));
|
|
2156
|
+
if (copyOk) {
|
|
2157
|
+
return newValue;
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2144
2160
|
const data = await this.get(key);
|
|
2145
2161
|
const value = (data?.value || 0) + amount;
|
|
2146
2162
|
await this.set(key, { value }, options);
|
|
@@ -19140,7 +19156,17 @@ class Client extends EventEmitter {
|
|
|
19140
19156
|
Bucket: this.config.bucket,
|
|
19141
19157
|
Key: keyPrefix ? path$1.join(keyPrefix, key) : key
|
|
19142
19158
|
};
|
|
19143
|
-
const [ok, err, response] = await tryFn(() =>
|
|
19159
|
+
const [ok, err, response] = await tryFn(async () => {
|
|
19160
|
+
const res = await this.sendCommand(new HeadObjectCommand(options));
|
|
19161
|
+
if (res.Metadata) {
|
|
19162
|
+
const decodedMetadata = {};
|
|
19163
|
+
for (const [key2, value] of Object.entries(res.Metadata)) {
|
|
19164
|
+
decodedMetadata[key2] = metadataDecode(value);
|
|
19165
|
+
}
|
|
19166
|
+
res.Metadata = decodedMetadata;
|
|
19167
|
+
}
|
|
19168
|
+
return res;
|
|
19169
|
+
});
|
|
19144
19170
|
this.emit("headObject", err || response, { key });
|
|
19145
19171
|
if (!ok) {
|
|
19146
19172
|
throw mapAwsError(err, {
|
|
@@ -19152,14 +19178,29 @@ class Client extends EventEmitter {
|
|
|
19152
19178
|
}
|
|
19153
19179
|
return response;
|
|
19154
19180
|
}
|
|
19155
|
-
async copyObject({ from, to }) {
|
|
19181
|
+
async copyObject({ from, to, metadata, metadataDirective, contentType }) {
|
|
19182
|
+
const keyPrefix = typeof this.config.keyPrefix === "string" ? this.config.keyPrefix : "";
|
|
19156
19183
|
const options = {
|
|
19157
19184
|
Bucket: this.config.bucket,
|
|
19158
|
-
Key:
|
|
19159
|
-
CopySource: path$1.join(this.config.bucket,
|
|
19185
|
+
Key: keyPrefix ? path$1.join(keyPrefix, to) : to,
|
|
19186
|
+
CopySource: path$1.join(this.config.bucket, keyPrefix ? path$1.join(keyPrefix, from) : from)
|
|
19160
19187
|
};
|
|
19188
|
+
if (metadataDirective) {
|
|
19189
|
+
options.MetadataDirective = metadataDirective;
|
|
19190
|
+
}
|
|
19191
|
+
if (metadata && typeof metadata === "object") {
|
|
19192
|
+
const encodedMetadata = {};
|
|
19193
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
19194
|
+
const { encoded } = metadataEncode(value);
|
|
19195
|
+
encodedMetadata[key] = encoded;
|
|
19196
|
+
}
|
|
19197
|
+
options.Metadata = encodedMetadata;
|
|
19198
|
+
}
|
|
19199
|
+
if (contentType) {
|
|
19200
|
+
options.ContentType = contentType;
|
|
19201
|
+
}
|
|
19161
19202
|
const [ok, err, response] = await tryFn(() => this.sendCommand(new CopyObjectCommand(options)));
|
|
19162
|
-
this.emit("copyObject", err || response, { from, to });
|
|
19203
|
+
this.emit("copyObject", err || response, { from, to, metadataDirective });
|
|
19163
19204
|
if (!ok) {
|
|
19164
19205
|
throw mapAwsError(err, {
|
|
19165
19206
|
bucket: this.config.bucket,
|
|
@@ -22362,6 +22403,235 @@ ${errorDetails}`,
|
|
|
22362
22403
|
return finalResult;
|
|
22363
22404
|
}
|
|
22364
22405
|
}
|
|
22406
|
+
/**
|
|
22407
|
+
* Patch resource (partial update optimized for metadata-only behaviors)
|
|
22408
|
+
*
|
|
22409
|
+
* This method provides an optimized update path for resources using metadata-only behaviors
|
|
22410
|
+
* (enforce-limits, truncate-data). It uses HeadObject + CopyObject for atomic updates without
|
|
22411
|
+
* body transfer, eliminating race conditions and reducing latency by ~50%.
|
|
22412
|
+
*
|
|
22413
|
+
* For behaviors that store data in body (body-overflow, body-only), it automatically falls
|
|
22414
|
+
* back to the standard update() method.
|
|
22415
|
+
*
|
|
22416
|
+
* @param {string} id - Resource ID
|
|
22417
|
+
* @param {Object} fields - Fields to update (partial data)
|
|
22418
|
+
* @param {Object} options - Update options
|
|
22419
|
+
* @param {string} options.partition - Partition name (if using partitions)
|
|
22420
|
+
* @param {Object} options.partitionValues - Partition values (if using partitions)
|
|
22421
|
+
* @returns {Promise<Object>} Updated resource data
|
|
22422
|
+
*
|
|
22423
|
+
* @example
|
|
22424
|
+
* // Fast atomic update (enforce-limits behavior)
|
|
22425
|
+
* await resource.patch('user-123', { status: 'active', loginCount: 42 });
|
|
22426
|
+
*
|
|
22427
|
+
* @example
|
|
22428
|
+
* // With partitions
|
|
22429
|
+
* await resource.patch('order-456', { status: 'shipped' }, {
|
|
22430
|
+
* partition: 'byRegion',
|
|
22431
|
+
* partitionValues: { region: 'US' }
|
|
22432
|
+
* });
|
|
22433
|
+
*/
|
|
22434
|
+
async patch(id, fields, options = {}) {
|
|
22435
|
+
if (isEmpty(id)) {
|
|
22436
|
+
throw new Error("id cannot be empty");
|
|
22437
|
+
}
|
|
22438
|
+
if (!fields || typeof fields !== "object") {
|
|
22439
|
+
throw new Error("fields must be a non-empty object");
|
|
22440
|
+
}
|
|
22441
|
+
const behavior = this.behavior;
|
|
22442
|
+
const hasNestedFields = Object.keys(fields).some((key) => key.includes("."));
|
|
22443
|
+
if ((behavior === "enforce-limits" || behavior === "truncate-data") && !hasNestedFields) {
|
|
22444
|
+
return await this._patchViaCopyObject(id, fields, options);
|
|
22445
|
+
}
|
|
22446
|
+
return await this.update(id, fields, options);
|
|
22447
|
+
}
|
|
22448
|
+
/**
|
|
22449
|
+
* Internal helper: Optimized patch using HeadObject + CopyObject
|
|
22450
|
+
* Only works for metadata-only behaviors (enforce-limits, truncate-data)
|
|
22451
|
+
* Only for simple field updates (no nested fields with dot notation)
|
|
22452
|
+
* @private
|
|
22453
|
+
*/
|
|
22454
|
+
async _patchViaCopyObject(id, fields, options = {}) {
|
|
22455
|
+
const { partition, partitionValues } = options;
|
|
22456
|
+
const key = this.getResourceKey(id);
|
|
22457
|
+
const headResponse = await this.client.headObject(key);
|
|
22458
|
+
const currentMetadata = headResponse.Metadata || {};
|
|
22459
|
+
let currentData = await this.schema.unmapper(currentMetadata);
|
|
22460
|
+
if (!currentData.id) {
|
|
22461
|
+
currentData.id = id;
|
|
22462
|
+
}
|
|
22463
|
+
const fieldsClone = cloneDeep(fields);
|
|
22464
|
+
let mergedData = cloneDeep(currentData);
|
|
22465
|
+
for (const [key2, value] of Object.entries(fieldsClone)) {
|
|
22466
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
22467
|
+
mergedData[key2] = merge({}, mergedData[key2], value);
|
|
22468
|
+
} else {
|
|
22469
|
+
mergedData[key2] = cloneDeep(value);
|
|
22470
|
+
}
|
|
22471
|
+
}
|
|
22472
|
+
if (this.config.timestamps) {
|
|
22473
|
+
mergedData.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
22474
|
+
}
|
|
22475
|
+
const validationResult = await this.schema.validate(mergedData);
|
|
22476
|
+
if (validationResult !== true) {
|
|
22477
|
+
throw new ValidationError("Validation failed during patch", validationResult);
|
|
22478
|
+
}
|
|
22479
|
+
const newMetadata = await this.schema.mapper(mergedData);
|
|
22480
|
+
newMetadata._v = String(this.version);
|
|
22481
|
+
await this.client.copyObject({
|
|
22482
|
+
from: key,
|
|
22483
|
+
to: key,
|
|
22484
|
+
metadataDirective: "REPLACE",
|
|
22485
|
+
metadata: newMetadata
|
|
22486
|
+
});
|
|
22487
|
+
if (this.config.partitions && Object.keys(this.config.partitions).length > 0) {
|
|
22488
|
+
const oldData = { ...currentData, id };
|
|
22489
|
+
const newData = { ...mergedData, id };
|
|
22490
|
+
if (this.config.asyncPartitions) {
|
|
22491
|
+
setImmediate(() => {
|
|
22492
|
+
this.handlePartitionReferenceUpdates(oldData, newData).catch((err) => {
|
|
22493
|
+
this.emit("partitionIndexError", {
|
|
22494
|
+
operation: "patch",
|
|
22495
|
+
id,
|
|
22496
|
+
error: err
|
|
22497
|
+
});
|
|
22498
|
+
});
|
|
22499
|
+
});
|
|
22500
|
+
} else {
|
|
22501
|
+
await this.handlePartitionReferenceUpdates(oldData, newData);
|
|
22502
|
+
}
|
|
22503
|
+
}
|
|
22504
|
+
return mergedData;
|
|
22505
|
+
}
|
|
22506
|
+
/**
|
|
22507
|
+
* Replace resource (full object replacement without GET)
|
|
22508
|
+
*
|
|
22509
|
+
* This method performs a direct PUT operation without fetching the current object.
|
|
22510
|
+
* Use this when you already have the complete object and want to replace it entirely,
|
|
22511
|
+
* saving 1 S3 request (GET).
|
|
22512
|
+
*
|
|
22513
|
+
* ⚠️ Warning: You must provide ALL required fields. Missing fields will NOT be preserved
|
|
22514
|
+
* from the current object. This method does not merge with existing data.
|
|
22515
|
+
*
|
|
22516
|
+
* @param {string} id - Resource ID
|
|
22517
|
+
* @param {Object} fullData - Complete object data (all required fields)
|
|
22518
|
+
* @param {Object} options - Update options
|
|
22519
|
+
* @param {string} options.partition - Partition name (if using partitions)
|
|
22520
|
+
* @param {Object} options.partitionValues - Partition values (if using partitions)
|
|
22521
|
+
* @returns {Promise<Object>} Replaced resource data
|
|
22522
|
+
*
|
|
22523
|
+
* @example
|
|
22524
|
+
* // Replace entire object (must include ALL required fields)
|
|
22525
|
+
* await resource.replace('user-123', {
|
|
22526
|
+
* name: 'John Doe',
|
|
22527
|
+
* email: 'john@example.com',
|
|
22528
|
+
* status: 'active',
|
|
22529
|
+
* loginCount: 42
|
|
22530
|
+
* });
|
|
22531
|
+
*
|
|
22532
|
+
* @example
|
|
22533
|
+
* // With partitions
|
|
22534
|
+
* await resource.replace('order-456', fullOrderData, {
|
|
22535
|
+
* partition: 'byRegion',
|
|
22536
|
+
* partitionValues: { region: 'US' }
|
|
22537
|
+
* });
|
|
22538
|
+
*/
|
|
22539
|
+
async replace(id, fullData, options = {}) {
|
|
22540
|
+
if (isEmpty(id)) {
|
|
22541
|
+
throw new Error("id cannot be empty");
|
|
22542
|
+
}
|
|
22543
|
+
if (!fullData || typeof fullData !== "object") {
|
|
22544
|
+
throw new Error("fullData must be a non-empty object");
|
|
22545
|
+
}
|
|
22546
|
+
const { partition, partitionValues } = options;
|
|
22547
|
+
const dataClone = cloneDeep(fullData);
|
|
22548
|
+
const attributesWithDefaults = this.applyDefaults(dataClone);
|
|
22549
|
+
if (this.config.timestamps) {
|
|
22550
|
+
if (!attributesWithDefaults.createdAt) {
|
|
22551
|
+
attributesWithDefaults.createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
22552
|
+
}
|
|
22553
|
+
attributesWithDefaults.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
22554
|
+
}
|
|
22555
|
+
const completeData = { id, ...attributesWithDefaults };
|
|
22556
|
+
const {
|
|
22557
|
+
errors,
|
|
22558
|
+
isValid,
|
|
22559
|
+
data: validated
|
|
22560
|
+
} = await this.validate(completeData);
|
|
22561
|
+
if (!isValid) {
|
|
22562
|
+
const errorMsg = errors && errors.length && errors[0].message ? errors[0].message : "Replace failed";
|
|
22563
|
+
throw new InvalidResourceItem({
|
|
22564
|
+
bucket: this.client.config.bucket,
|
|
22565
|
+
resourceName: this.name,
|
|
22566
|
+
attributes: completeData,
|
|
22567
|
+
validation: errors,
|
|
22568
|
+
message: errorMsg
|
|
22569
|
+
});
|
|
22570
|
+
}
|
|
22571
|
+
const { id: validatedId, ...validatedAttributes } = validated;
|
|
22572
|
+
const mappedMetadata = await this.schema.mapper(validatedAttributes);
|
|
22573
|
+
mappedMetadata._v = String(this.version);
|
|
22574
|
+
const behaviorImpl = getBehavior(this.behavior);
|
|
22575
|
+
const { mappedData: finalMetadata, body } = await behaviorImpl.handleInsert({
|
|
22576
|
+
resource: this,
|
|
22577
|
+
data: validatedAttributes,
|
|
22578
|
+
mappedData: mappedMetadata,
|
|
22579
|
+
originalData: completeData
|
|
22580
|
+
});
|
|
22581
|
+
const key = this.getResourceKey(id);
|
|
22582
|
+
let contentType = void 0;
|
|
22583
|
+
if (body && body !== "") {
|
|
22584
|
+
const [okParse] = await tryFn(() => Promise.resolve(JSON.parse(body)));
|
|
22585
|
+
if (okParse) contentType = "application/json";
|
|
22586
|
+
}
|
|
22587
|
+
if (this.behavior === "body-only" && (!body || body === "")) {
|
|
22588
|
+
throw new Error(`[Resource.replace] Attempt to save object without body! Data: id=${id}, resource=${this.name}`);
|
|
22589
|
+
}
|
|
22590
|
+
const [okPut, errPut] = await tryFn(() => this.client.putObject({
|
|
22591
|
+
key,
|
|
22592
|
+
body,
|
|
22593
|
+
contentType,
|
|
22594
|
+
metadata: finalMetadata
|
|
22595
|
+
}));
|
|
22596
|
+
if (!okPut) {
|
|
22597
|
+
const msg = errPut && errPut.message ? errPut.message : "";
|
|
22598
|
+
if (msg.includes("metadata headers exceed") || msg.includes("Replace failed")) {
|
|
22599
|
+
const totalSize = calculateTotalSize(finalMetadata);
|
|
22600
|
+
const effectiveLimit = calculateEffectiveLimit({
|
|
22601
|
+
s3Limit: 2047,
|
|
22602
|
+
systemConfig: {
|
|
22603
|
+
version: this.version,
|
|
22604
|
+
timestamps: this.config.timestamps,
|
|
22605
|
+
id
|
|
22606
|
+
}
|
|
22607
|
+
});
|
|
22608
|
+
const excess = totalSize - effectiveLimit;
|
|
22609
|
+
errPut.totalSize = totalSize;
|
|
22610
|
+
errPut.limit = 2047;
|
|
22611
|
+
errPut.effectiveLimit = effectiveLimit;
|
|
22612
|
+
errPut.excess = excess;
|
|
22613
|
+
throw new ResourceError("metadata headers exceed", { resourceName: this.name, operation: "replace", id, totalSize, effectiveLimit, excess, suggestion: "Reduce metadata size or number of fields." });
|
|
22614
|
+
}
|
|
22615
|
+
throw errPut;
|
|
22616
|
+
}
|
|
22617
|
+
const replacedObject = { id, ...validatedAttributes };
|
|
22618
|
+
if (this.config.partitions && Object.keys(this.config.partitions).length > 0) {
|
|
22619
|
+
if (this.config.asyncPartitions) {
|
|
22620
|
+
setImmediate(() => {
|
|
22621
|
+
this.handlePartitionReferenceUpdates({}, replacedObject).catch((err) => {
|
|
22622
|
+
this.emit("partitionIndexError", {
|
|
22623
|
+
operation: "replace",
|
|
22624
|
+
id,
|
|
22625
|
+
error: err
|
|
22626
|
+
});
|
|
22627
|
+
});
|
|
22628
|
+
});
|
|
22629
|
+
} else {
|
|
22630
|
+
await this.handlePartitionReferenceUpdates({}, replacedObject);
|
|
22631
|
+
}
|
|
22632
|
+
}
|
|
22633
|
+
return replacedObject;
|
|
22634
|
+
}
|
|
22365
22635
|
/**
|
|
22366
22636
|
* Update with conditional check (If-Match ETag)
|
|
22367
22637
|
* @param {string} id - Resource ID
|
|
@@ -23710,29 +23980,6 @@ ${errorDetails}`,
|
|
|
23710
23980
|
}
|
|
23711
23981
|
return filtered;
|
|
23712
23982
|
}
|
|
23713
|
-
async replace(id, attributes) {
|
|
23714
|
-
await this.delete(id);
|
|
23715
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
23716
|
-
const maxWait = 5e3;
|
|
23717
|
-
const interval = 50;
|
|
23718
|
-
const start = Date.now();
|
|
23719
|
-
while (Date.now() - start < maxWait) {
|
|
23720
|
-
const exists = await this.exists(id);
|
|
23721
|
-
if (!exists) {
|
|
23722
|
-
break;
|
|
23723
|
-
}
|
|
23724
|
-
await new Promise((r) => setTimeout(r, interval));
|
|
23725
|
-
}
|
|
23726
|
-
const [ok, err, result] = await tryFn(() => this.insert({ ...attributes, id }));
|
|
23727
|
-
if (!ok) {
|
|
23728
|
-
if (err && err.message && err.message.includes("already exists")) {
|
|
23729
|
-
const updateResult = await this.update(id, attributes);
|
|
23730
|
-
return updateResult;
|
|
23731
|
-
}
|
|
23732
|
-
throw err;
|
|
23733
|
-
}
|
|
23734
|
-
return result;
|
|
23735
|
-
}
|
|
23736
23983
|
// --- MIDDLEWARE SYSTEM ---
|
|
23737
23984
|
_initMiddleware() {
|
|
23738
23985
|
this._middlewares = /* @__PURE__ */ new Map();
|
|
@@ -23934,7 +24181,7 @@ class Database extends EventEmitter {
|
|
|
23934
24181
|
this.id = idGenerator(7);
|
|
23935
24182
|
this.version = "1";
|
|
23936
24183
|
this.s3dbVersion = (() => {
|
|
23937
|
-
const [ok, err, version] = tryFn(() => true ? "12.0.
|
|
24184
|
+
const [ok, err, version] = tryFn(() => true ? "12.0.1" : "latest");
|
|
23938
24185
|
return ok ? version : "latest";
|
|
23939
24186
|
})();
|
|
23940
24187
|
this.resources = {};
|
|
@@ -26804,7 +27051,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
26804
27051
|
async updateReplicatorLog(logId, updates) {
|
|
26805
27052
|
if (!this.replicatorLog) return;
|
|
26806
27053
|
const [ok, err] = await tryFn(async () => {
|
|
26807
|
-
await this.replicatorLog.
|
|
27054
|
+
await this.replicatorLog.patch(logId, {
|
|
26808
27055
|
...updates,
|
|
26809
27056
|
lastAttempt: (/* @__PURE__ */ new Date()).toISOString()
|
|
26810
27057
|
});
|
|
@@ -39784,6 +40031,9 @@ var runner = {};
|
|
|
39784
40031
|
|
|
39785
40032
|
var createId = {};
|
|
39786
40033
|
|
|
40034
|
+
const require = createRequire(import.meta.url);
|
|
40035
|
+
function __require() { return require("node:crypto"); }
|
|
40036
|
+
|
|
39787
40037
|
var hasRequiredCreateId;
|
|
39788
40038
|
|
|
39789
40039
|
function requireCreateId () {
|
|
@@ -39794,7 +40044,7 @@ function requireCreateId () {
|
|
|
39794
40044
|
};
|
|
39795
40045
|
Object.defineProperty(createId, "__esModule", { value: true });
|
|
39796
40046
|
createId.createID = createID;
|
|
39797
|
-
const node_crypto_1 = __importDefault(
|
|
40047
|
+
const node_crypto_1 = __importDefault(__require());
|
|
39798
40048
|
function createID(prefix = '', length = 16) {
|
|
39799
40049
|
const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
39800
40050
|
const values = node_crypto_1.default.randomBytes(length);
|