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.cjs.js
CHANGED
|
@@ -31,10 +31,11 @@ var promises$2 = require('node:fs/promises');
|
|
|
31
31
|
var node_events = require('node:events');
|
|
32
32
|
var Stream = require('node:stream');
|
|
33
33
|
var node_string_decoder = require('node:string_decoder');
|
|
34
|
-
var
|
|
34
|
+
var node_module = require('node:module');
|
|
35
35
|
var require$$1 = require('child_process');
|
|
36
36
|
var require$$5 = require('url');
|
|
37
37
|
|
|
38
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
38
39
|
function _interopNamespaceDefault(e) {
|
|
39
40
|
var n = Object.create(null);
|
|
40
41
|
if (e) {
|
|
@@ -1986,44 +1987,31 @@ class PluginStorage {
|
|
|
1986
1987
|
* @returns {Promise<boolean>} True if extended, false if not found or no TTL
|
|
1987
1988
|
*/
|
|
1988
1989
|
async touch(key, additionalSeconds) {
|
|
1989
|
-
const [ok, err, response] = await tryFn(() => this.client.
|
|
1990
|
+
const [ok, err, response] = await tryFn(() => this.client.headObject(key));
|
|
1990
1991
|
if (!ok) {
|
|
1991
1992
|
return false;
|
|
1992
1993
|
}
|
|
1993
1994
|
const metadata = response.Metadata || {};
|
|
1994
1995
|
const parsedMetadata = this._parseMetadataValues(metadata);
|
|
1995
|
-
|
|
1996
|
-
if (response.Body) {
|
|
1997
|
-
const [ok2, err2, result] = await tryFn(async () => {
|
|
1998
|
-
const bodyContent = await response.Body.transformToString();
|
|
1999
|
-
if (bodyContent && bodyContent.trim()) {
|
|
2000
|
-
const body = JSON.parse(bodyContent);
|
|
2001
|
-
return { ...parsedMetadata, ...body };
|
|
2002
|
-
}
|
|
2003
|
-
return parsedMetadata;
|
|
2004
|
-
});
|
|
2005
|
-
if (!ok2) {
|
|
2006
|
-
return false;
|
|
2007
|
-
}
|
|
2008
|
-
data = result;
|
|
2009
|
-
}
|
|
2010
|
-
const expiresAt = data._expiresat || data._expiresAt;
|
|
1996
|
+
const expiresAt = parsedMetadata._expiresat || parsedMetadata._expiresAt;
|
|
2011
1997
|
if (!expiresAt) {
|
|
2012
1998
|
return false;
|
|
2013
1999
|
}
|
|
2014
|
-
|
|
2015
|
-
delete
|
|
2016
|
-
const
|
|
2017
|
-
const
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2000
|
+
parsedMetadata._expiresAt = expiresAt + additionalSeconds * 1e3;
|
|
2001
|
+
delete parsedMetadata._expiresat;
|
|
2002
|
+
const encodedMetadata = {};
|
|
2003
|
+
for (const [metaKey, metaValue] of Object.entries(parsedMetadata)) {
|
|
2004
|
+
const { encoded } = metadataEncode(metaValue);
|
|
2005
|
+
encodedMetadata[metaKey] = encoded;
|
|
2006
|
+
}
|
|
2007
|
+
const [copyOk] = await tryFn(() => this.client.copyObject({
|
|
2008
|
+
from: key,
|
|
2009
|
+
to: key,
|
|
2010
|
+
metadata: encodedMetadata,
|
|
2011
|
+
metadataDirective: "REPLACE",
|
|
2012
|
+
contentType: response.ContentType || "application/json"
|
|
2013
|
+
}));
|
|
2014
|
+
return copyOk;
|
|
2027
2015
|
}
|
|
2028
2016
|
/**
|
|
2029
2017
|
* Delete a single object
|
|
@@ -2158,12 +2146,41 @@ class PluginStorage {
|
|
|
2158
2146
|
/**
|
|
2159
2147
|
* Increment a counter value
|
|
2160
2148
|
*
|
|
2149
|
+
* Optimization: Uses HEAD + COPY for existing counters to avoid body transfer.
|
|
2150
|
+
* Falls back to GET + PUT for non-existent counters or those with additional data.
|
|
2151
|
+
*
|
|
2161
2152
|
* @param {string} key - S3 key
|
|
2162
2153
|
* @param {number} amount - Amount to increment (default: 1)
|
|
2163
2154
|
* @param {Object} options - Options (e.g., ttl)
|
|
2164
2155
|
* @returns {Promise<number>} New value
|
|
2165
2156
|
*/
|
|
2166
2157
|
async increment(key, amount = 1, options = {}) {
|
|
2158
|
+
const [headOk, headErr, headResponse] = await tryFn(() => this.client.headObject(key));
|
|
2159
|
+
if (headOk && headResponse.Metadata) {
|
|
2160
|
+
const metadata = headResponse.Metadata || {};
|
|
2161
|
+
const parsedMetadata = this._parseMetadataValues(metadata);
|
|
2162
|
+
const currentValue = parsedMetadata.value || 0;
|
|
2163
|
+
const newValue = currentValue + amount;
|
|
2164
|
+
parsedMetadata.value = newValue;
|
|
2165
|
+
if (options.ttl) {
|
|
2166
|
+
parsedMetadata._expiresAt = Date.now() + options.ttl * 1e3;
|
|
2167
|
+
}
|
|
2168
|
+
const encodedMetadata = {};
|
|
2169
|
+
for (const [metaKey, metaValue] of Object.entries(parsedMetadata)) {
|
|
2170
|
+
const { encoded } = metadataEncode(metaValue);
|
|
2171
|
+
encodedMetadata[metaKey] = encoded;
|
|
2172
|
+
}
|
|
2173
|
+
const [copyOk] = await tryFn(() => this.client.copyObject({
|
|
2174
|
+
from: key,
|
|
2175
|
+
to: key,
|
|
2176
|
+
metadata: encodedMetadata,
|
|
2177
|
+
metadataDirective: "REPLACE",
|
|
2178
|
+
contentType: headResponse.ContentType || "application/json"
|
|
2179
|
+
}));
|
|
2180
|
+
if (copyOk) {
|
|
2181
|
+
return newValue;
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2167
2184
|
const data = await this.get(key);
|
|
2168
2185
|
const value = (data?.value || 0) + amount;
|
|
2169
2186
|
await this.set(key, { value }, options);
|
|
@@ -19163,7 +19180,17 @@ class Client extends EventEmitter {
|
|
|
19163
19180
|
Bucket: this.config.bucket,
|
|
19164
19181
|
Key: keyPrefix ? path$1.join(keyPrefix, key) : key
|
|
19165
19182
|
};
|
|
19166
|
-
const [ok, err, response] = await tryFn(() =>
|
|
19183
|
+
const [ok, err, response] = await tryFn(async () => {
|
|
19184
|
+
const res = await this.sendCommand(new clientS3.HeadObjectCommand(options));
|
|
19185
|
+
if (res.Metadata) {
|
|
19186
|
+
const decodedMetadata = {};
|
|
19187
|
+
for (const [key2, value] of Object.entries(res.Metadata)) {
|
|
19188
|
+
decodedMetadata[key2] = metadataDecode(value);
|
|
19189
|
+
}
|
|
19190
|
+
res.Metadata = decodedMetadata;
|
|
19191
|
+
}
|
|
19192
|
+
return res;
|
|
19193
|
+
});
|
|
19167
19194
|
this.emit("headObject", err || response, { key });
|
|
19168
19195
|
if (!ok) {
|
|
19169
19196
|
throw mapAwsError(err, {
|
|
@@ -19175,14 +19202,29 @@ class Client extends EventEmitter {
|
|
|
19175
19202
|
}
|
|
19176
19203
|
return response;
|
|
19177
19204
|
}
|
|
19178
|
-
async copyObject({ from, to }) {
|
|
19205
|
+
async copyObject({ from, to, metadata, metadataDirective, contentType }) {
|
|
19206
|
+
const keyPrefix = typeof this.config.keyPrefix === "string" ? this.config.keyPrefix : "";
|
|
19179
19207
|
const options = {
|
|
19180
19208
|
Bucket: this.config.bucket,
|
|
19181
|
-
Key:
|
|
19182
|
-
CopySource: path$1.join(this.config.bucket,
|
|
19209
|
+
Key: keyPrefix ? path$1.join(keyPrefix, to) : to,
|
|
19210
|
+
CopySource: path$1.join(this.config.bucket, keyPrefix ? path$1.join(keyPrefix, from) : from)
|
|
19183
19211
|
};
|
|
19212
|
+
if (metadataDirective) {
|
|
19213
|
+
options.MetadataDirective = metadataDirective;
|
|
19214
|
+
}
|
|
19215
|
+
if (metadata && typeof metadata === "object") {
|
|
19216
|
+
const encodedMetadata = {};
|
|
19217
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
19218
|
+
const { encoded } = metadataEncode(value);
|
|
19219
|
+
encodedMetadata[key] = encoded;
|
|
19220
|
+
}
|
|
19221
|
+
options.Metadata = encodedMetadata;
|
|
19222
|
+
}
|
|
19223
|
+
if (contentType) {
|
|
19224
|
+
options.ContentType = contentType;
|
|
19225
|
+
}
|
|
19184
19226
|
const [ok, err, response] = await tryFn(() => this.sendCommand(new clientS3.CopyObjectCommand(options)));
|
|
19185
|
-
this.emit("copyObject", err || response, { from, to });
|
|
19227
|
+
this.emit("copyObject", err || response, { from, to, metadataDirective });
|
|
19186
19228
|
if (!ok) {
|
|
19187
19229
|
throw mapAwsError(err, {
|
|
19188
19230
|
bucket: this.config.bucket,
|
|
@@ -22385,6 +22427,235 @@ ${errorDetails}`,
|
|
|
22385
22427
|
return finalResult;
|
|
22386
22428
|
}
|
|
22387
22429
|
}
|
|
22430
|
+
/**
|
|
22431
|
+
* Patch resource (partial update optimized for metadata-only behaviors)
|
|
22432
|
+
*
|
|
22433
|
+
* This method provides an optimized update path for resources using metadata-only behaviors
|
|
22434
|
+
* (enforce-limits, truncate-data). It uses HeadObject + CopyObject for atomic updates without
|
|
22435
|
+
* body transfer, eliminating race conditions and reducing latency by ~50%.
|
|
22436
|
+
*
|
|
22437
|
+
* For behaviors that store data in body (body-overflow, body-only), it automatically falls
|
|
22438
|
+
* back to the standard update() method.
|
|
22439
|
+
*
|
|
22440
|
+
* @param {string} id - Resource ID
|
|
22441
|
+
* @param {Object} fields - Fields to update (partial data)
|
|
22442
|
+
* @param {Object} options - Update options
|
|
22443
|
+
* @param {string} options.partition - Partition name (if using partitions)
|
|
22444
|
+
* @param {Object} options.partitionValues - Partition values (if using partitions)
|
|
22445
|
+
* @returns {Promise<Object>} Updated resource data
|
|
22446
|
+
*
|
|
22447
|
+
* @example
|
|
22448
|
+
* // Fast atomic update (enforce-limits behavior)
|
|
22449
|
+
* await resource.patch('user-123', { status: 'active', loginCount: 42 });
|
|
22450
|
+
*
|
|
22451
|
+
* @example
|
|
22452
|
+
* // With partitions
|
|
22453
|
+
* await resource.patch('order-456', { status: 'shipped' }, {
|
|
22454
|
+
* partition: 'byRegion',
|
|
22455
|
+
* partitionValues: { region: 'US' }
|
|
22456
|
+
* });
|
|
22457
|
+
*/
|
|
22458
|
+
async patch(id, fields, options = {}) {
|
|
22459
|
+
if (lodashEs.isEmpty(id)) {
|
|
22460
|
+
throw new Error("id cannot be empty");
|
|
22461
|
+
}
|
|
22462
|
+
if (!fields || typeof fields !== "object") {
|
|
22463
|
+
throw new Error("fields must be a non-empty object");
|
|
22464
|
+
}
|
|
22465
|
+
const behavior = this.behavior;
|
|
22466
|
+
const hasNestedFields = Object.keys(fields).some((key) => key.includes("."));
|
|
22467
|
+
if ((behavior === "enforce-limits" || behavior === "truncate-data") && !hasNestedFields) {
|
|
22468
|
+
return await this._patchViaCopyObject(id, fields, options);
|
|
22469
|
+
}
|
|
22470
|
+
return await this.update(id, fields, options);
|
|
22471
|
+
}
|
|
22472
|
+
/**
|
|
22473
|
+
* Internal helper: Optimized patch using HeadObject + CopyObject
|
|
22474
|
+
* Only works for metadata-only behaviors (enforce-limits, truncate-data)
|
|
22475
|
+
* Only for simple field updates (no nested fields with dot notation)
|
|
22476
|
+
* @private
|
|
22477
|
+
*/
|
|
22478
|
+
async _patchViaCopyObject(id, fields, options = {}) {
|
|
22479
|
+
const { partition, partitionValues } = options;
|
|
22480
|
+
const key = this.getResourceKey(id);
|
|
22481
|
+
const headResponse = await this.client.headObject(key);
|
|
22482
|
+
const currentMetadata = headResponse.Metadata || {};
|
|
22483
|
+
let currentData = await this.schema.unmapper(currentMetadata);
|
|
22484
|
+
if (!currentData.id) {
|
|
22485
|
+
currentData.id = id;
|
|
22486
|
+
}
|
|
22487
|
+
const fieldsClone = lodashEs.cloneDeep(fields);
|
|
22488
|
+
let mergedData = lodashEs.cloneDeep(currentData);
|
|
22489
|
+
for (const [key2, value] of Object.entries(fieldsClone)) {
|
|
22490
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
22491
|
+
mergedData[key2] = lodashEs.merge({}, mergedData[key2], value);
|
|
22492
|
+
} else {
|
|
22493
|
+
mergedData[key2] = lodashEs.cloneDeep(value);
|
|
22494
|
+
}
|
|
22495
|
+
}
|
|
22496
|
+
if (this.config.timestamps) {
|
|
22497
|
+
mergedData.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
22498
|
+
}
|
|
22499
|
+
const validationResult = await this.schema.validate(mergedData);
|
|
22500
|
+
if (validationResult !== true) {
|
|
22501
|
+
throw new ValidationError("Validation failed during patch", validationResult);
|
|
22502
|
+
}
|
|
22503
|
+
const newMetadata = await this.schema.mapper(mergedData);
|
|
22504
|
+
newMetadata._v = String(this.version);
|
|
22505
|
+
await this.client.copyObject({
|
|
22506
|
+
from: key,
|
|
22507
|
+
to: key,
|
|
22508
|
+
metadataDirective: "REPLACE",
|
|
22509
|
+
metadata: newMetadata
|
|
22510
|
+
});
|
|
22511
|
+
if (this.config.partitions && Object.keys(this.config.partitions).length > 0) {
|
|
22512
|
+
const oldData = { ...currentData, id };
|
|
22513
|
+
const newData = { ...mergedData, id };
|
|
22514
|
+
if (this.config.asyncPartitions) {
|
|
22515
|
+
setImmediate(() => {
|
|
22516
|
+
this.handlePartitionReferenceUpdates(oldData, newData).catch((err) => {
|
|
22517
|
+
this.emit("partitionIndexError", {
|
|
22518
|
+
operation: "patch",
|
|
22519
|
+
id,
|
|
22520
|
+
error: err
|
|
22521
|
+
});
|
|
22522
|
+
});
|
|
22523
|
+
});
|
|
22524
|
+
} else {
|
|
22525
|
+
await this.handlePartitionReferenceUpdates(oldData, newData);
|
|
22526
|
+
}
|
|
22527
|
+
}
|
|
22528
|
+
return mergedData;
|
|
22529
|
+
}
|
|
22530
|
+
/**
|
|
22531
|
+
* Replace resource (full object replacement without GET)
|
|
22532
|
+
*
|
|
22533
|
+
* This method performs a direct PUT operation without fetching the current object.
|
|
22534
|
+
* Use this when you already have the complete object and want to replace it entirely,
|
|
22535
|
+
* saving 1 S3 request (GET).
|
|
22536
|
+
*
|
|
22537
|
+
* ⚠️ Warning: You must provide ALL required fields. Missing fields will NOT be preserved
|
|
22538
|
+
* from the current object. This method does not merge with existing data.
|
|
22539
|
+
*
|
|
22540
|
+
* @param {string} id - Resource ID
|
|
22541
|
+
* @param {Object} fullData - Complete object data (all required fields)
|
|
22542
|
+
* @param {Object} options - Update options
|
|
22543
|
+
* @param {string} options.partition - Partition name (if using partitions)
|
|
22544
|
+
* @param {Object} options.partitionValues - Partition values (if using partitions)
|
|
22545
|
+
* @returns {Promise<Object>} Replaced resource data
|
|
22546
|
+
*
|
|
22547
|
+
* @example
|
|
22548
|
+
* // Replace entire object (must include ALL required fields)
|
|
22549
|
+
* await resource.replace('user-123', {
|
|
22550
|
+
* name: 'John Doe',
|
|
22551
|
+
* email: 'john@example.com',
|
|
22552
|
+
* status: 'active',
|
|
22553
|
+
* loginCount: 42
|
|
22554
|
+
* });
|
|
22555
|
+
*
|
|
22556
|
+
* @example
|
|
22557
|
+
* // With partitions
|
|
22558
|
+
* await resource.replace('order-456', fullOrderData, {
|
|
22559
|
+
* partition: 'byRegion',
|
|
22560
|
+
* partitionValues: { region: 'US' }
|
|
22561
|
+
* });
|
|
22562
|
+
*/
|
|
22563
|
+
async replace(id, fullData, options = {}) {
|
|
22564
|
+
if (lodashEs.isEmpty(id)) {
|
|
22565
|
+
throw new Error("id cannot be empty");
|
|
22566
|
+
}
|
|
22567
|
+
if (!fullData || typeof fullData !== "object") {
|
|
22568
|
+
throw new Error("fullData must be a non-empty object");
|
|
22569
|
+
}
|
|
22570
|
+
const { partition, partitionValues } = options;
|
|
22571
|
+
const dataClone = lodashEs.cloneDeep(fullData);
|
|
22572
|
+
const attributesWithDefaults = this.applyDefaults(dataClone);
|
|
22573
|
+
if (this.config.timestamps) {
|
|
22574
|
+
if (!attributesWithDefaults.createdAt) {
|
|
22575
|
+
attributesWithDefaults.createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
22576
|
+
}
|
|
22577
|
+
attributesWithDefaults.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
22578
|
+
}
|
|
22579
|
+
const completeData = { id, ...attributesWithDefaults };
|
|
22580
|
+
const {
|
|
22581
|
+
errors,
|
|
22582
|
+
isValid,
|
|
22583
|
+
data: validated
|
|
22584
|
+
} = await this.validate(completeData);
|
|
22585
|
+
if (!isValid) {
|
|
22586
|
+
const errorMsg = errors && errors.length && errors[0].message ? errors[0].message : "Replace failed";
|
|
22587
|
+
throw new InvalidResourceItem({
|
|
22588
|
+
bucket: this.client.config.bucket,
|
|
22589
|
+
resourceName: this.name,
|
|
22590
|
+
attributes: completeData,
|
|
22591
|
+
validation: errors,
|
|
22592
|
+
message: errorMsg
|
|
22593
|
+
});
|
|
22594
|
+
}
|
|
22595
|
+
const { id: validatedId, ...validatedAttributes } = validated;
|
|
22596
|
+
const mappedMetadata = await this.schema.mapper(validatedAttributes);
|
|
22597
|
+
mappedMetadata._v = String(this.version);
|
|
22598
|
+
const behaviorImpl = getBehavior(this.behavior);
|
|
22599
|
+
const { mappedData: finalMetadata, body } = await behaviorImpl.handleInsert({
|
|
22600
|
+
resource: this,
|
|
22601
|
+
data: validatedAttributes,
|
|
22602
|
+
mappedData: mappedMetadata,
|
|
22603
|
+
originalData: completeData
|
|
22604
|
+
});
|
|
22605
|
+
const key = this.getResourceKey(id);
|
|
22606
|
+
let contentType = void 0;
|
|
22607
|
+
if (body && body !== "") {
|
|
22608
|
+
const [okParse] = await tryFn(() => Promise.resolve(JSON.parse(body)));
|
|
22609
|
+
if (okParse) contentType = "application/json";
|
|
22610
|
+
}
|
|
22611
|
+
if (this.behavior === "body-only" && (!body || body === "")) {
|
|
22612
|
+
throw new Error(`[Resource.replace] Attempt to save object without body! Data: id=${id}, resource=${this.name}`);
|
|
22613
|
+
}
|
|
22614
|
+
const [okPut, errPut] = await tryFn(() => this.client.putObject({
|
|
22615
|
+
key,
|
|
22616
|
+
body,
|
|
22617
|
+
contentType,
|
|
22618
|
+
metadata: finalMetadata
|
|
22619
|
+
}));
|
|
22620
|
+
if (!okPut) {
|
|
22621
|
+
const msg = errPut && errPut.message ? errPut.message : "";
|
|
22622
|
+
if (msg.includes("metadata headers exceed") || msg.includes("Replace failed")) {
|
|
22623
|
+
const totalSize = calculateTotalSize(finalMetadata);
|
|
22624
|
+
const effectiveLimit = calculateEffectiveLimit({
|
|
22625
|
+
s3Limit: 2047,
|
|
22626
|
+
systemConfig: {
|
|
22627
|
+
version: this.version,
|
|
22628
|
+
timestamps: this.config.timestamps,
|
|
22629
|
+
id
|
|
22630
|
+
}
|
|
22631
|
+
});
|
|
22632
|
+
const excess = totalSize - effectiveLimit;
|
|
22633
|
+
errPut.totalSize = totalSize;
|
|
22634
|
+
errPut.limit = 2047;
|
|
22635
|
+
errPut.effectiveLimit = effectiveLimit;
|
|
22636
|
+
errPut.excess = excess;
|
|
22637
|
+
throw new ResourceError("metadata headers exceed", { resourceName: this.name, operation: "replace", id, totalSize, effectiveLimit, excess, suggestion: "Reduce metadata size or number of fields." });
|
|
22638
|
+
}
|
|
22639
|
+
throw errPut;
|
|
22640
|
+
}
|
|
22641
|
+
const replacedObject = { id, ...validatedAttributes };
|
|
22642
|
+
if (this.config.partitions && Object.keys(this.config.partitions).length > 0) {
|
|
22643
|
+
if (this.config.asyncPartitions) {
|
|
22644
|
+
setImmediate(() => {
|
|
22645
|
+
this.handlePartitionReferenceUpdates({}, replacedObject).catch((err) => {
|
|
22646
|
+
this.emit("partitionIndexError", {
|
|
22647
|
+
operation: "replace",
|
|
22648
|
+
id,
|
|
22649
|
+
error: err
|
|
22650
|
+
});
|
|
22651
|
+
});
|
|
22652
|
+
});
|
|
22653
|
+
} else {
|
|
22654
|
+
await this.handlePartitionReferenceUpdates({}, replacedObject);
|
|
22655
|
+
}
|
|
22656
|
+
}
|
|
22657
|
+
return replacedObject;
|
|
22658
|
+
}
|
|
22388
22659
|
/**
|
|
22389
22660
|
* Update with conditional check (If-Match ETag)
|
|
22390
22661
|
* @param {string} id - Resource ID
|
|
@@ -23733,29 +24004,6 @@ ${errorDetails}`,
|
|
|
23733
24004
|
}
|
|
23734
24005
|
return filtered;
|
|
23735
24006
|
}
|
|
23736
|
-
async replace(id, attributes) {
|
|
23737
|
-
await this.delete(id);
|
|
23738
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
23739
|
-
const maxWait = 5e3;
|
|
23740
|
-
const interval = 50;
|
|
23741
|
-
const start = Date.now();
|
|
23742
|
-
while (Date.now() - start < maxWait) {
|
|
23743
|
-
const exists = await this.exists(id);
|
|
23744
|
-
if (!exists) {
|
|
23745
|
-
break;
|
|
23746
|
-
}
|
|
23747
|
-
await new Promise((r) => setTimeout(r, interval));
|
|
23748
|
-
}
|
|
23749
|
-
const [ok, err, result] = await tryFn(() => this.insert({ ...attributes, id }));
|
|
23750
|
-
if (!ok) {
|
|
23751
|
-
if (err && err.message && err.message.includes("already exists")) {
|
|
23752
|
-
const updateResult = await this.update(id, attributes);
|
|
23753
|
-
return updateResult;
|
|
23754
|
-
}
|
|
23755
|
-
throw err;
|
|
23756
|
-
}
|
|
23757
|
-
return result;
|
|
23758
|
-
}
|
|
23759
24007
|
// --- MIDDLEWARE SYSTEM ---
|
|
23760
24008
|
_initMiddleware() {
|
|
23761
24009
|
this._middlewares = /* @__PURE__ */ new Map();
|
|
@@ -23957,7 +24205,7 @@ class Database extends EventEmitter {
|
|
|
23957
24205
|
this.id = idGenerator(7);
|
|
23958
24206
|
this.version = "1";
|
|
23959
24207
|
this.s3dbVersion = (() => {
|
|
23960
|
-
const [ok, err, version] = tryFn(() => true ? "12.0.
|
|
24208
|
+
const [ok, err, version] = tryFn(() => true ? "12.0.1" : "latest");
|
|
23961
24209
|
return ok ? version : "latest";
|
|
23962
24210
|
})();
|
|
23963
24211
|
this.resources = {};
|
|
@@ -26827,7 +27075,7 @@ class ReplicatorPlugin extends Plugin {
|
|
|
26827
27075
|
async updateReplicatorLog(logId, updates) {
|
|
26828
27076
|
if (!this.replicatorLog) return;
|
|
26829
27077
|
const [ok, err] = await tryFn(async () => {
|
|
26830
|
-
await this.replicatorLog.
|
|
27078
|
+
await this.replicatorLog.patch(logId, {
|
|
26831
27079
|
...updates,
|
|
26832
27080
|
lastAttempt: (/* @__PURE__ */ new Date()).toISOString()
|
|
26833
27081
|
});
|
|
@@ -39807,6 +40055,9 @@ var runner = {};
|
|
|
39807
40055
|
|
|
39808
40056
|
var createId = {};
|
|
39809
40057
|
|
|
40058
|
+
const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('s3db.cjs.js', document.baseURI).href)));
|
|
40059
|
+
function __require() { return require$1("node:crypto"); }
|
|
40060
|
+
|
|
39810
40061
|
var hasRequiredCreateId;
|
|
39811
40062
|
|
|
39812
40063
|
function requireCreateId () {
|
|
@@ -39817,7 +40068,7 @@ function requireCreateId () {
|
|
|
39817
40068
|
};
|
|
39818
40069
|
Object.defineProperty(createId, "__esModule", { value: true });
|
|
39819
40070
|
createId.createID = createID;
|
|
39820
|
-
const node_crypto_1 = __importDefault(
|
|
40071
|
+
const node_crypto_1 = __importDefault(__require());
|
|
39821
40072
|
function createID(prefix = '', length = 16) {
|
|
39822
40073
|
const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
39823
40074
|
const values = node_crypto_1.default.randomBytes(length);
|