s3db.js 8.1.0 → 8.1.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/PLUGINS.md +95 -34
- package/dist/s3db.cjs.js +123 -53
- package/dist/s3db.cjs.min.js +1 -1
- package/dist/s3db.es.js +123 -53
- package/dist/s3db.es.min.js +1 -1
- package/dist/s3db.iife.js +123 -53
- package/dist/s3db.iife.min.js +1 -1
- package/package.json +2 -2
- package/src/behaviors/body-overflow.js +0 -1
- package/src/behaviors/enforce-limits.js +3 -1
- package/src/behaviors/truncate-data.js +2 -1
- package/src/behaviors/user-managed.js +24 -3
- package/src/plugins/audit.plugin.js +61 -10
- package/src/plugins/cache/partition-aware-filesystem-cache.class.js +28 -0
- package/src/plugins/cache/s3-cache.class.js +1 -1
- package/src/resource.class.js +38 -38
package/dist/s3db.iife.js
CHANGED
|
@@ -605,6 +605,12 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
605
605
|
const passwordAlphabet = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789";
|
|
606
606
|
const passwordGenerator = nanoid.customAlphabet(passwordAlphabet, 16);
|
|
607
607
|
|
|
608
|
+
var id = /*#__PURE__*/Object.freeze({
|
|
609
|
+
__proto__: null,
|
|
610
|
+
idGenerator: idGenerator,
|
|
611
|
+
passwordGenerator: passwordGenerator
|
|
612
|
+
});
|
|
613
|
+
|
|
608
614
|
var domain;
|
|
609
615
|
|
|
610
616
|
// This constructor is used to store event handlers. Instantiating this is
|
|
@@ -1298,14 +1304,15 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
1298
1304
|
}
|
|
1299
1305
|
setupResourceAuditing(resource) {
|
|
1300
1306
|
resource.on("insert", async (data) => {
|
|
1307
|
+
const partitionValues = this.config.includePartitions ? this.getPartitionValues(data, resource) : null;
|
|
1301
1308
|
await this.logAudit({
|
|
1302
1309
|
resourceName: resource.name,
|
|
1303
1310
|
operation: "insert",
|
|
1304
1311
|
recordId: data.id || "auto-generated",
|
|
1305
1312
|
oldData: null,
|
|
1306
1313
|
newData: this.config.includeData ? JSON.stringify(this.truncateData(data)) : null,
|
|
1307
|
-
partition:
|
|
1308
|
-
partitionValues:
|
|
1314
|
+
partition: partitionValues ? this.getPrimaryPartition(partitionValues) : null,
|
|
1315
|
+
partitionValues: partitionValues ? JSON.stringify(partitionValues) : null
|
|
1309
1316
|
});
|
|
1310
1317
|
});
|
|
1311
1318
|
resource.on("update", async (data) => {
|
|
@@ -1314,14 +1321,15 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
1314
1321
|
const [ok, err, fetched] = await try_fn_default(() => resource.get(data.id));
|
|
1315
1322
|
if (ok) oldData = fetched;
|
|
1316
1323
|
}
|
|
1324
|
+
const partitionValues = this.config.includePartitions ? this.getPartitionValues(data, resource) : null;
|
|
1317
1325
|
await this.logAudit({
|
|
1318
1326
|
resourceName: resource.name,
|
|
1319
1327
|
operation: "update",
|
|
1320
1328
|
recordId: data.id,
|
|
1321
1329
|
oldData: oldData && this.config.includeData ? JSON.stringify(this.truncateData(oldData)) : null,
|
|
1322
1330
|
newData: this.config.includeData ? JSON.stringify(this.truncateData(data)) : null,
|
|
1323
|
-
partition:
|
|
1324
|
-
partitionValues:
|
|
1331
|
+
partition: partitionValues ? this.getPrimaryPartition(partitionValues) : null,
|
|
1332
|
+
partitionValues: partitionValues ? JSON.stringify(partitionValues) : null
|
|
1325
1333
|
});
|
|
1326
1334
|
});
|
|
1327
1335
|
resource.on("delete", async (data) => {
|
|
@@ -1330,16 +1338,49 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
1330
1338
|
const [ok, err, fetched] = await try_fn_default(() => resource.get(data.id));
|
|
1331
1339
|
if (ok) oldData = fetched;
|
|
1332
1340
|
}
|
|
1341
|
+
const partitionValues = oldData && this.config.includePartitions ? this.getPartitionValues(oldData, resource) : null;
|
|
1333
1342
|
await this.logAudit({
|
|
1334
1343
|
resourceName: resource.name,
|
|
1335
1344
|
operation: "delete",
|
|
1336
1345
|
recordId: data.id,
|
|
1337
1346
|
oldData: oldData && this.config.includeData ? JSON.stringify(this.truncateData(oldData)) : null,
|
|
1338
1347
|
newData: null,
|
|
1339
|
-
partition:
|
|
1340
|
-
partitionValues:
|
|
1348
|
+
partition: partitionValues ? this.getPrimaryPartition(partitionValues) : null,
|
|
1349
|
+
partitionValues: partitionValues ? JSON.stringify(partitionValues) : null
|
|
1341
1350
|
});
|
|
1342
1351
|
});
|
|
1352
|
+
const originalDeleteMany = resource.deleteMany.bind(resource);
|
|
1353
|
+
const plugin = this;
|
|
1354
|
+
resource.deleteMany = async function(ids) {
|
|
1355
|
+
const objectsToDelete = [];
|
|
1356
|
+
if (plugin.config.includeData) {
|
|
1357
|
+
for (const id of ids) {
|
|
1358
|
+
const [ok, err, fetched] = await try_fn_default(() => resource.get(id));
|
|
1359
|
+
if (ok) {
|
|
1360
|
+
objectsToDelete.push(fetched);
|
|
1361
|
+
} else {
|
|
1362
|
+
objectsToDelete.push({ id });
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
} else {
|
|
1366
|
+
objectsToDelete.push(...ids.map((id) => ({ id })));
|
|
1367
|
+
}
|
|
1368
|
+
const result = await originalDeleteMany(ids);
|
|
1369
|
+
for (const oldData of objectsToDelete) {
|
|
1370
|
+
const partitionValues = oldData && plugin.config.includePartitions ? plugin.getPartitionValues(oldData, resource) : null;
|
|
1371
|
+
await plugin.logAudit({
|
|
1372
|
+
resourceName: resource.name,
|
|
1373
|
+
operation: "deleteMany",
|
|
1374
|
+
recordId: oldData.id,
|
|
1375
|
+
oldData: oldData && plugin.config.includeData ? JSON.stringify(plugin.truncateData(oldData)) : null,
|
|
1376
|
+
newData: null,
|
|
1377
|
+
partition: partitionValues ? plugin.getPrimaryPartition(partitionValues) : null,
|
|
1378
|
+
partitionValues: partitionValues ? JSON.stringify(partitionValues) : null
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
1381
|
+
return result;
|
|
1382
|
+
};
|
|
1383
|
+
resource._originalDeleteMany = originalDeleteMany;
|
|
1343
1384
|
}
|
|
1344
1385
|
// Backward compatibility for tests
|
|
1345
1386
|
installEventListenersForResource(resource) {
|
|
@@ -1377,9 +1418,13 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
1377
1418
|
}
|
|
1378
1419
|
}
|
|
1379
1420
|
getPartitionValues(data, resource) {
|
|
1380
|
-
if (!this.config.includePartitions
|
|
1421
|
+
if (!this.config.includePartitions) return null;
|
|
1422
|
+
const partitions = resource.config?.partitions || resource.partitions;
|
|
1423
|
+
if (!partitions) {
|
|
1424
|
+
return null;
|
|
1425
|
+
}
|
|
1381
1426
|
const partitionValues = {};
|
|
1382
|
-
for (const [partitionName, partitionConfig] of Object.entries(
|
|
1427
|
+
for (const [partitionName, partitionConfig] of Object.entries(partitions)) {
|
|
1383
1428
|
const values = {};
|
|
1384
1429
|
for (const field of Object.keys(partitionConfig.fields)) {
|
|
1385
1430
|
values[field] = this.getNestedFieldValue(data, field);
|
|
@@ -1422,7 +1467,7 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
1422
1467
|
}
|
|
1423
1468
|
async getAuditLogs(options = {}) {
|
|
1424
1469
|
if (!this.auditResource) return [];
|
|
1425
|
-
const { resourceName, operation, recordId, partition, startDate, endDate, limit = 100 } = options;
|
|
1470
|
+
const { resourceName, operation, recordId, partition, startDate, endDate, limit = 100, offset = 0 } = options;
|
|
1426
1471
|
let query = {};
|
|
1427
1472
|
if (resourceName) query.resourceName = resourceName;
|
|
1428
1473
|
if (operation) query.operation = operation;
|
|
@@ -1433,7 +1478,7 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
1433
1478
|
if (startDate) query.timestamp.$gte = startDate;
|
|
1434
1479
|
if (endDate) query.timestamp.$lte = endDate;
|
|
1435
1480
|
}
|
|
1436
|
-
const result = await this.auditResource.page({ query, limit });
|
|
1481
|
+
const result = await this.auditResource.page({ query, limit, offset });
|
|
1437
1482
|
return result.items || [];
|
|
1438
1483
|
}
|
|
1439
1484
|
async getRecordHistory(resourceName, recordId) {
|
|
@@ -6548,7 +6593,7 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
6548
6593
|
ttl = 0,
|
|
6549
6594
|
prefix = void 0
|
|
6550
6595
|
}) {
|
|
6551
|
-
super(
|
|
6596
|
+
super();
|
|
6552
6597
|
this.client = client;
|
|
6553
6598
|
this.keyPrefix = keyPrefix;
|
|
6554
6599
|
this.config.ttl = ttl;
|
|
@@ -7229,6 +7274,26 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
7229
7274
|
}
|
|
7230
7275
|
return super._set(key, data);
|
|
7231
7276
|
}
|
|
7277
|
+
/**
|
|
7278
|
+
* Public set method with partition support
|
|
7279
|
+
*/
|
|
7280
|
+
async set(resource, action, data, options = {}) {
|
|
7281
|
+
if (typeof resource === "string" && typeof action === "string" && options.partition) {
|
|
7282
|
+
const key = this._getPartitionCacheKey(resource, action, options.partition, options.partitionValues, options.params);
|
|
7283
|
+
return this._set(key, data, { resource, action, ...options });
|
|
7284
|
+
}
|
|
7285
|
+
return super.set(resource, action);
|
|
7286
|
+
}
|
|
7287
|
+
/**
|
|
7288
|
+
* Public get method with partition support
|
|
7289
|
+
*/
|
|
7290
|
+
async get(resource, action, options = {}) {
|
|
7291
|
+
if (typeof resource === "string" && typeof action === "string" && options.partition) {
|
|
7292
|
+
const key = this._getPartitionCacheKey(resource, action, options.partition, options.partitionValues, options.params);
|
|
7293
|
+
return this._get(key, { resource, action, ...options });
|
|
7294
|
+
}
|
|
7295
|
+
return super.get(resource);
|
|
7296
|
+
}
|
|
7232
7297
|
/**
|
|
7233
7298
|
* Enhanced get method with partition awareness
|
|
7234
7299
|
*/
|
|
@@ -10791,7 +10856,7 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
10791
10856
|
if (totalSize > effectiveLimit) {
|
|
10792
10857
|
throw new Error(`S3 metadata size exceeds 2KB limit. Current size: ${totalSize} bytes, effective limit: ${effectiveLimit} bytes, absolute limit: ${S3_METADATA_LIMIT_BYTES} bytes`);
|
|
10793
10858
|
}
|
|
10794
|
-
return { mappedData, body:
|
|
10859
|
+
return { mappedData, body: "" };
|
|
10795
10860
|
}
|
|
10796
10861
|
async function handleUpdate$4({ resource, id, data, mappedData, originalData }) {
|
|
10797
10862
|
const totalSize = calculateTotalSize(mappedData);
|
|
@@ -10854,8 +10919,9 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
10854
10919
|
excess: totalSize - 2047,
|
|
10855
10920
|
data: originalData || data
|
|
10856
10921
|
});
|
|
10922
|
+
return { mappedData: { _v: mappedData._v }, body: JSON.stringify(mappedData) };
|
|
10857
10923
|
}
|
|
10858
|
-
return { mappedData, body:
|
|
10924
|
+
return { mappedData, body: "" };
|
|
10859
10925
|
}
|
|
10860
10926
|
async function handleUpdate$3({ resource, id, data, mappedData, originalData }) {
|
|
10861
10927
|
const totalSize = calculateTotalSize(mappedData);
|
|
@@ -10902,6 +10968,18 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
10902
10968
|
return { mappedData, body: JSON.stringify(data) };
|
|
10903
10969
|
}
|
|
10904
10970
|
async function handleGet$3({ resource, metadata, body }) {
|
|
10971
|
+
if (body && body.trim() !== "") {
|
|
10972
|
+
try {
|
|
10973
|
+
const bodyData = JSON.parse(body);
|
|
10974
|
+
const mergedData = {
|
|
10975
|
+
...bodyData,
|
|
10976
|
+
...metadata
|
|
10977
|
+
};
|
|
10978
|
+
return { metadata: mergedData, body };
|
|
10979
|
+
} catch (error) {
|
|
10980
|
+
return { metadata, body };
|
|
10981
|
+
}
|
|
10982
|
+
}
|
|
10905
10983
|
return { metadata, body };
|
|
10906
10984
|
}
|
|
10907
10985
|
|
|
@@ -10969,7 +11047,7 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
10969
11047
|
if (truncated) {
|
|
10970
11048
|
resultFields[TRUNCATED_FLAG] = TRUNCATED_FLAG_VALUE;
|
|
10971
11049
|
}
|
|
10972
|
-
return { mappedData: resultFields, body:
|
|
11050
|
+
return { mappedData: resultFields, body: "" };
|
|
10973
11051
|
}
|
|
10974
11052
|
async function handleUpdate$2({ resource, id, data, mappedData, originalData }) {
|
|
10975
11053
|
return handleInsert$2({ resource, data, mappedData, originalData });
|
|
@@ -11059,7 +11137,6 @@ ${JSON.stringify(validation, null, 2)}`,
|
|
|
11059
11137
|
}
|
|
11060
11138
|
const hasOverflow = Object.keys(bodyFields).length > 0;
|
|
11061
11139
|
let body = hasOverflow ? JSON.stringify(bodyFields) : "";
|
|
11062
|
-
if (!hasOverflow) body = "{}";
|
|
11063
11140
|
return { mappedData: metadataFields, body };
|
|
11064
11141
|
}
|
|
11065
11142
|
async function handleUpdate$1({ resource, id, data, mappedData, originalData }) {
|
|
@@ -11711,16 +11788,16 @@ ${errorDetails}`,
|
|
|
11711
11788
|
* email: 'john@example.com'
|
|
11712
11789
|
* });
|
|
11713
11790
|
*/
|
|
11714
|
-
async insert({ id, ...attributes }) {
|
|
11715
|
-
const exists = await this.exists(id);
|
|
11716
|
-
if (exists) throw new Error(`Resource with id '${id}' already exists`);
|
|
11717
|
-
this.getResourceKey(id || "(auto)");
|
|
11791
|
+
async insert({ id: id$1, ...attributes }) {
|
|
11792
|
+
const exists = await this.exists(id$1);
|
|
11793
|
+
if (exists) throw new Error(`Resource with id '${id$1}' already exists`);
|
|
11794
|
+
this.getResourceKey(id$1 || "(auto)");
|
|
11718
11795
|
if (this.options.timestamps) {
|
|
11719
11796
|
attributes.createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
11720
11797
|
attributes.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
11721
11798
|
}
|
|
11722
11799
|
const attributesWithDefaults = this.applyDefaults(attributes);
|
|
11723
|
-
const completeData = { id, ...attributesWithDefaults };
|
|
11800
|
+
const completeData = { id: id$1, ...attributesWithDefaults };
|
|
11724
11801
|
const preProcessedData = await this.executeHooks("beforeInsert", completeData);
|
|
11725
11802
|
const extraProps = Object.keys(preProcessedData).filter(
|
|
11726
11803
|
(k) => !(k in completeData) || preProcessedData[k] !== completeData[k]
|
|
@@ -11744,7 +11821,14 @@ ${errorDetails}`,
|
|
|
11744
11821
|
}
|
|
11745
11822
|
const { id: validatedId, ...validatedAttributes } = validated;
|
|
11746
11823
|
Object.assign(validatedAttributes, extraData);
|
|
11747
|
-
|
|
11824
|
+
let finalId = validatedId || id$1;
|
|
11825
|
+
if (!finalId) {
|
|
11826
|
+
finalId = this.idGenerator();
|
|
11827
|
+
if (!finalId || finalId.trim() === "") {
|
|
11828
|
+
const { idGenerator } = await Promise.resolve().then(function () { return id; });
|
|
11829
|
+
finalId = idGenerator();
|
|
11830
|
+
}
|
|
11831
|
+
}
|
|
11748
11832
|
const mappedData = await this.schema.mapper(validatedAttributes);
|
|
11749
11833
|
mappedData._v = String(this.version);
|
|
11750
11834
|
const behaviorImpl = getBehavior(this.behavior);
|
|
@@ -11789,26 +11873,11 @@ ${errorDetails}`,
|
|
|
11789
11873
|
errPut.excess = excess;
|
|
11790
11874
|
throw new ResourceError("metadata headers exceed", { resourceName: this.name, operation: "insert", id: finalId, totalSize, effectiveLimit, excess, suggestion: "Reduce metadata size or number of fields." });
|
|
11791
11875
|
}
|
|
11792
|
-
throw
|
|
11793
|
-
bucket: this.client.config.bucket,
|
|
11794
|
-
key,
|
|
11795
|
-
resourceName: this.name,
|
|
11796
|
-
operation: "insert",
|
|
11797
|
-
id: finalId
|
|
11798
|
-
});
|
|
11876
|
+
throw errPut;
|
|
11799
11877
|
}
|
|
11800
|
-
|
|
11801
|
-
|
|
11802
|
-
|
|
11803
|
-
body,
|
|
11804
|
-
behavior: this.behavior
|
|
11805
|
-
});
|
|
11806
|
-
const finalResult = await this.executeHooks("afterInsert", insertedData);
|
|
11807
|
-
this.emit("insert", {
|
|
11808
|
-
...insertedData,
|
|
11809
|
-
$before: { ...completeData },
|
|
11810
|
-
$after: { ...finalResult }
|
|
11811
|
-
});
|
|
11878
|
+
const insertedObject = await this.get(finalId);
|
|
11879
|
+
const finalResult = await this.executeHooks("afterInsert", insertedObject);
|
|
11880
|
+
this.emit("insert", finalResult);
|
|
11812
11881
|
return finalResult;
|
|
11813
11882
|
}
|
|
11814
11883
|
/**
|
|
@@ -11817,7 +11886,7 @@ ${errorDetails}`,
|
|
|
11817
11886
|
* @returns {Promise<Object>} The resource object with all attributes and metadata
|
|
11818
11887
|
* @example
|
|
11819
11888
|
* const user = await resource.get('user-123');
|
|
11820
|
-
|
|
11889
|
+
*/
|
|
11821
11890
|
async get(id) {
|
|
11822
11891
|
if (lodashEs.isObject(id)) throw new Error(`id cannot be an object`);
|
|
11823
11892
|
if (lodashEs.isEmpty(id)) throw new Error("id cannot be empty");
|
|
@@ -11832,17 +11901,6 @@ ${errorDetails}`,
|
|
|
11832
11901
|
id
|
|
11833
11902
|
});
|
|
11834
11903
|
}
|
|
11835
|
-
if (request.ContentLength === 0) {
|
|
11836
|
-
const noContentErr = new Error(`No such key: ${key} [bucket:${this.client.config.bucket}]`);
|
|
11837
|
-
noContentErr.name = "NoSuchKey";
|
|
11838
|
-
throw mapAwsError(noContentErr, {
|
|
11839
|
-
bucket: this.client.config.bucket,
|
|
11840
|
-
key,
|
|
11841
|
-
resourceName: this.name,
|
|
11842
|
-
operation: "get",
|
|
11843
|
-
id
|
|
11844
|
-
});
|
|
11845
|
-
}
|
|
11846
11904
|
const objectVersionRaw = request.Metadata?._v || this.version;
|
|
11847
11905
|
const objectVersion = typeof objectVersionRaw === "string" && objectVersionRaw.startsWith("v") ? objectVersionRaw.slice(1) : objectVersionRaw;
|
|
11848
11906
|
const schema = await this.getSchemaForVersion(objectVersion);
|
|
@@ -13181,6 +13239,18 @@ ${errorDetails}`,
|
|
|
13181
13239
|
});
|
|
13182
13240
|
return result2;
|
|
13183
13241
|
}
|
|
13242
|
+
if (behavior === "user-managed" && body && body.trim() !== "") {
|
|
13243
|
+
const [okBody, errBody, parsedBody] = await try_fn_default(() => Promise.resolve(JSON.parse(body)));
|
|
13244
|
+
if (okBody) {
|
|
13245
|
+
const [okUnmap, errUnmap, unmappedBody] = await try_fn_default(() => this.schema.unmapper(parsedBody));
|
|
13246
|
+
const bodyData = okUnmap ? unmappedBody : {};
|
|
13247
|
+
const merged = { ...bodyData, ...unmappedMetadata, id };
|
|
13248
|
+
Object.keys(merged).forEach((k) => {
|
|
13249
|
+
merged[k] = fixValue(merged[k]);
|
|
13250
|
+
});
|
|
13251
|
+
return filterInternalFields(merged);
|
|
13252
|
+
}
|
|
13253
|
+
}
|
|
13184
13254
|
const result = { ...unmappedMetadata, id };
|
|
13185
13255
|
Object.keys(result).forEach((k) => {
|
|
13186
13256
|
result[k] = fixValue(result[k]);
|
|
@@ -13423,7 +13493,7 @@ ${errorDetails}`,
|
|
|
13423
13493
|
this.id = idGenerator(7);
|
|
13424
13494
|
this.version = "1";
|
|
13425
13495
|
this.s3dbVersion = (() => {
|
|
13426
|
-
const [ok, err, version] = try_fn_default(() => true ? "8.1.
|
|
13496
|
+
const [ok, err, version] = try_fn_default(() => true ? "8.1.1" : "latest");
|
|
13427
13497
|
return ok ? version : "latest";
|
|
13428
13498
|
})();
|
|
13429
13499
|
this.resources = {};
|