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.es.js
CHANGED
|
@@ -615,6 +615,12 @@ const idGenerator = customAlphabet(urlAlphabet, 22);
|
|
|
615
615
|
const passwordAlphabet = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789";
|
|
616
616
|
const passwordGenerator = customAlphabet(passwordAlphabet, 16);
|
|
617
617
|
|
|
618
|
+
var id = /*#__PURE__*/Object.freeze({
|
|
619
|
+
__proto__: null,
|
|
620
|
+
idGenerator: idGenerator,
|
|
621
|
+
passwordGenerator: passwordGenerator
|
|
622
|
+
});
|
|
623
|
+
|
|
618
624
|
var domain;
|
|
619
625
|
|
|
620
626
|
// This constructor is used to store event handlers. Instantiating this is
|
|
@@ -1308,14 +1314,15 @@ class AuditPlugin extends plugin_class_default {
|
|
|
1308
1314
|
}
|
|
1309
1315
|
setupResourceAuditing(resource) {
|
|
1310
1316
|
resource.on("insert", async (data) => {
|
|
1317
|
+
const partitionValues = this.config.includePartitions ? this.getPartitionValues(data, resource) : null;
|
|
1311
1318
|
await this.logAudit({
|
|
1312
1319
|
resourceName: resource.name,
|
|
1313
1320
|
operation: "insert",
|
|
1314
1321
|
recordId: data.id || "auto-generated",
|
|
1315
1322
|
oldData: null,
|
|
1316
1323
|
newData: this.config.includeData ? JSON.stringify(this.truncateData(data)) : null,
|
|
1317
|
-
partition:
|
|
1318
|
-
partitionValues:
|
|
1324
|
+
partition: partitionValues ? this.getPrimaryPartition(partitionValues) : null,
|
|
1325
|
+
partitionValues: partitionValues ? JSON.stringify(partitionValues) : null
|
|
1319
1326
|
});
|
|
1320
1327
|
});
|
|
1321
1328
|
resource.on("update", async (data) => {
|
|
@@ -1324,14 +1331,15 @@ class AuditPlugin extends plugin_class_default {
|
|
|
1324
1331
|
const [ok, err, fetched] = await try_fn_default(() => resource.get(data.id));
|
|
1325
1332
|
if (ok) oldData = fetched;
|
|
1326
1333
|
}
|
|
1334
|
+
const partitionValues = this.config.includePartitions ? this.getPartitionValues(data, resource) : null;
|
|
1327
1335
|
await this.logAudit({
|
|
1328
1336
|
resourceName: resource.name,
|
|
1329
1337
|
operation: "update",
|
|
1330
1338
|
recordId: data.id,
|
|
1331
1339
|
oldData: oldData && this.config.includeData ? JSON.stringify(this.truncateData(oldData)) : null,
|
|
1332
1340
|
newData: this.config.includeData ? JSON.stringify(this.truncateData(data)) : null,
|
|
1333
|
-
partition:
|
|
1334
|
-
partitionValues:
|
|
1341
|
+
partition: partitionValues ? this.getPrimaryPartition(partitionValues) : null,
|
|
1342
|
+
partitionValues: partitionValues ? JSON.stringify(partitionValues) : null
|
|
1335
1343
|
});
|
|
1336
1344
|
});
|
|
1337
1345
|
resource.on("delete", async (data) => {
|
|
@@ -1340,16 +1348,49 @@ class AuditPlugin extends plugin_class_default {
|
|
|
1340
1348
|
const [ok, err, fetched] = await try_fn_default(() => resource.get(data.id));
|
|
1341
1349
|
if (ok) oldData = fetched;
|
|
1342
1350
|
}
|
|
1351
|
+
const partitionValues = oldData && this.config.includePartitions ? this.getPartitionValues(oldData, resource) : null;
|
|
1343
1352
|
await this.logAudit({
|
|
1344
1353
|
resourceName: resource.name,
|
|
1345
1354
|
operation: "delete",
|
|
1346
1355
|
recordId: data.id,
|
|
1347
1356
|
oldData: oldData && this.config.includeData ? JSON.stringify(this.truncateData(oldData)) : null,
|
|
1348
1357
|
newData: null,
|
|
1349
|
-
partition:
|
|
1350
|
-
partitionValues:
|
|
1358
|
+
partition: partitionValues ? this.getPrimaryPartition(partitionValues) : null,
|
|
1359
|
+
partitionValues: partitionValues ? JSON.stringify(partitionValues) : null
|
|
1351
1360
|
});
|
|
1352
1361
|
});
|
|
1362
|
+
const originalDeleteMany = resource.deleteMany.bind(resource);
|
|
1363
|
+
const plugin = this;
|
|
1364
|
+
resource.deleteMany = async function(ids) {
|
|
1365
|
+
const objectsToDelete = [];
|
|
1366
|
+
if (plugin.config.includeData) {
|
|
1367
|
+
for (const id of ids) {
|
|
1368
|
+
const [ok, err, fetched] = await try_fn_default(() => resource.get(id));
|
|
1369
|
+
if (ok) {
|
|
1370
|
+
objectsToDelete.push(fetched);
|
|
1371
|
+
} else {
|
|
1372
|
+
objectsToDelete.push({ id });
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
} else {
|
|
1376
|
+
objectsToDelete.push(...ids.map((id) => ({ id })));
|
|
1377
|
+
}
|
|
1378
|
+
const result = await originalDeleteMany(ids);
|
|
1379
|
+
for (const oldData of objectsToDelete) {
|
|
1380
|
+
const partitionValues = oldData && plugin.config.includePartitions ? plugin.getPartitionValues(oldData, resource) : null;
|
|
1381
|
+
await plugin.logAudit({
|
|
1382
|
+
resourceName: resource.name,
|
|
1383
|
+
operation: "deleteMany",
|
|
1384
|
+
recordId: oldData.id,
|
|
1385
|
+
oldData: oldData && plugin.config.includeData ? JSON.stringify(plugin.truncateData(oldData)) : null,
|
|
1386
|
+
newData: null,
|
|
1387
|
+
partition: partitionValues ? plugin.getPrimaryPartition(partitionValues) : null,
|
|
1388
|
+
partitionValues: partitionValues ? JSON.stringify(partitionValues) : null
|
|
1389
|
+
});
|
|
1390
|
+
}
|
|
1391
|
+
return result;
|
|
1392
|
+
};
|
|
1393
|
+
resource._originalDeleteMany = originalDeleteMany;
|
|
1353
1394
|
}
|
|
1354
1395
|
// Backward compatibility for tests
|
|
1355
1396
|
installEventListenersForResource(resource) {
|
|
@@ -1387,9 +1428,13 @@ class AuditPlugin extends plugin_class_default {
|
|
|
1387
1428
|
}
|
|
1388
1429
|
}
|
|
1389
1430
|
getPartitionValues(data, resource) {
|
|
1390
|
-
if (!this.config.includePartitions
|
|
1431
|
+
if (!this.config.includePartitions) return null;
|
|
1432
|
+
const partitions = resource.config?.partitions || resource.partitions;
|
|
1433
|
+
if (!partitions) {
|
|
1434
|
+
return null;
|
|
1435
|
+
}
|
|
1391
1436
|
const partitionValues = {};
|
|
1392
|
-
for (const [partitionName, partitionConfig] of Object.entries(
|
|
1437
|
+
for (const [partitionName, partitionConfig] of Object.entries(partitions)) {
|
|
1393
1438
|
const values = {};
|
|
1394
1439
|
for (const field of Object.keys(partitionConfig.fields)) {
|
|
1395
1440
|
values[field] = this.getNestedFieldValue(data, field);
|
|
@@ -1432,7 +1477,7 @@ class AuditPlugin extends plugin_class_default {
|
|
|
1432
1477
|
}
|
|
1433
1478
|
async getAuditLogs(options = {}) {
|
|
1434
1479
|
if (!this.auditResource) return [];
|
|
1435
|
-
const { resourceName, operation, recordId, partition, startDate, endDate, limit = 100 } = options;
|
|
1480
|
+
const { resourceName, operation, recordId, partition, startDate, endDate, limit = 100, offset = 0 } = options;
|
|
1436
1481
|
let query = {};
|
|
1437
1482
|
if (resourceName) query.resourceName = resourceName;
|
|
1438
1483
|
if (operation) query.operation = operation;
|
|
@@ -1443,7 +1488,7 @@ class AuditPlugin extends plugin_class_default {
|
|
|
1443
1488
|
if (startDate) query.timestamp.$gte = startDate;
|
|
1444
1489
|
if (endDate) query.timestamp.$lte = endDate;
|
|
1445
1490
|
}
|
|
1446
|
-
const result = await this.auditResource.page({ query, limit });
|
|
1491
|
+
const result = await this.auditResource.page({ query, limit, offset });
|
|
1447
1492
|
return result.items || [];
|
|
1448
1493
|
}
|
|
1449
1494
|
async getRecordHistory(resourceName, recordId) {
|
|
@@ -6558,7 +6603,7 @@ class S3Cache extends Cache {
|
|
|
6558
6603
|
ttl = 0,
|
|
6559
6604
|
prefix = void 0
|
|
6560
6605
|
}) {
|
|
6561
|
-
super(
|
|
6606
|
+
super();
|
|
6562
6607
|
this.client = client;
|
|
6563
6608
|
this.keyPrefix = keyPrefix;
|
|
6564
6609
|
this.config.ttl = ttl;
|
|
@@ -7239,6 +7284,26 @@ class PartitionAwareFilesystemCache extends FilesystemCache {
|
|
|
7239
7284
|
}
|
|
7240
7285
|
return super._set(key, data);
|
|
7241
7286
|
}
|
|
7287
|
+
/**
|
|
7288
|
+
* Public set method with partition support
|
|
7289
|
+
*/
|
|
7290
|
+
async set(resource, action, data, 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._set(key, data, { resource, action, ...options });
|
|
7294
|
+
}
|
|
7295
|
+
return super.set(resource, action);
|
|
7296
|
+
}
|
|
7297
|
+
/**
|
|
7298
|
+
* Public get method with partition support
|
|
7299
|
+
*/
|
|
7300
|
+
async get(resource, action, options = {}) {
|
|
7301
|
+
if (typeof resource === "string" && typeof action === "string" && options.partition) {
|
|
7302
|
+
const key = this._getPartitionCacheKey(resource, action, options.partition, options.partitionValues, options.params);
|
|
7303
|
+
return this._get(key, { resource, action, ...options });
|
|
7304
|
+
}
|
|
7305
|
+
return super.get(resource);
|
|
7306
|
+
}
|
|
7242
7307
|
/**
|
|
7243
7308
|
* Enhanced get method with partition awareness
|
|
7244
7309
|
*/
|
|
@@ -10801,7 +10866,7 @@ async function handleInsert$4({ resource, data, mappedData, originalData }) {
|
|
|
10801
10866
|
if (totalSize > effectiveLimit) {
|
|
10802
10867
|
throw new Error(`S3 metadata size exceeds 2KB limit. Current size: ${totalSize} bytes, effective limit: ${effectiveLimit} bytes, absolute limit: ${S3_METADATA_LIMIT_BYTES} bytes`);
|
|
10803
10868
|
}
|
|
10804
|
-
return { mappedData, body:
|
|
10869
|
+
return { mappedData, body: "" };
|
|
10805
10870
|
}
|
|
10806
10871
|
async function handleUpdate$4({ resource, id, data, mappedData, originalData }) {
|
|
10807
10872
|
const totalSize = calculateTotalSize(mappedData);
|
|
@@ -10864,8 +10929,9 @@ async function handleInsert$3({ resource, data, mappedData, originalData }) {
|
|
|
10864
10929
|
excess: totalSize - 2047,
|
|
10865
10930
|
data: originalData || data
|
|
10866
10931
|
});
|
|
10932
|
+
return { mappedData: { _v: mappedData._v }, body: JSON.stringify(mappedData) };
|
|
10867
10933
|
}
|
|
10868
|
-
return { mappedData, body:
|
|
10934
|
+
return { mappedData, body: "" };
|
|
10869
10935
|
}
|
|
10870
10936
|
async function handleUpdate$3({ resource, id, data, mappedData, originalData }) {
|
|
10871
10937
|
const totalSize = calculateTotalSize(mappedData);
|
|
@@ -10912,6 +10978,18 @@ async function handleUpsert$3({ resource, id, data, mappedData, originalData })
|
|
|
10912
10978
|
return { mappedData, body: JSON.stringify(data) };
|
|
10913
10979
|
}
|
|
10914
10980
|
async function handleGet$3({ resource, metadata, body }) {
|
|
10981
|
+
if (body && body.trim() !== "") {
|
|
10982
|
+
try {
|
|
10983
|
+
const bodyData = JSON.parse(body);
|
|
10984
|
+
const mergedData = {
|
|
10985
|
+
...bodyData,
|
|
10986
|
+
...metadata
|
|
10987
|
+
};
|
|
10988
|
+
return { metadata: mergedData, body };
|
|
10989
|
+
} catch (error) {
|
|
10990
|
+
return { metadata, body };
|
|
10991
|
+
}
|
|
10992
|
+
}
|
|
10915
10993
|
return { metadata, body };
|
|
10916
10994
|
}
|
|
10917
10995
|
|
|
@@ -10979,7 +11057,7 @@ async function handleInsert$2({ resource, data, mappedData, originalData }) {
|
|
|
10979
11057
|
if (truncated) {
|
|
10980
11058
|
resultFields[TRUNCATED_FLAG] = TRUNCATED_FLAG_VALUE;
|
|
10981
11059
|
}
|
|
10982
|
-
return { mappedData: resultFields, body:
|
|
11060
|
+
return { mappedData: resultFields, body: "" };
|
|
10983
11061
|
}
|
|
10984
11062
|
async function handleUpdate$2({ resource, id, data, mappedData, originalData }) {
|
|
10985
11063
|
return handleInsert$2({ resource, data, mappedData, originalData });
|
|
@@ -11069,7 +11147,6 @@ async function handleInsert$1({ resource, data, mappedData, originalData }) {
|
|
|
11069
11147
|
}
|
|
11070
11148
|
const hasOverflow = Object.keys(bodyFields).length > 0;
|
|
11071
11149
|
let body = hasOverflow ? JSON.stringify(bodyFields) : "";
|
|
11072
|
-
if (!hasOverflow) body = "{}";
|
|
11073
11150
|
return { mappedData: metadataFields, body };
|
|
11074
11151
|
}
|
|
11075
11152
|
async function handleUpdate$1({ resource, id, data, mappedData, originalData }) {
|
|
@@ -11721,16 +11798,16 @@ ${errorDetails}`,
|
|
|
11721
11798
|
* email: 'john@example.com'
|
|
11722
11799
|
* });
|
|
11723
11800
|
*/
|
|
11724
|
-
async insert({ id, ...attributes }) {
|
|
11725
|
-
const exists = await this.exists(id);
|
|
11726
|
-
if (exists) throw new Error(`Resource with id '${id}' already exists`);
|
|
11727
|
-
this.getResourceKey(id || "(auto)");
|
|
11801
|
+
async insert({ id: id$1, ...attributes }) {
|
|
11802
|
+
const exists = await this.exists(id$1);
|
|
11803
|
+
if (exists) throw new Error(`Resource with id '${id$1}' already exists`);
|
|
11804
|
+
this.getResourceKey(id$1 || "(auto)");
|
|
11728
11805
|
if (this.options.timestamps) {
|
|
11729
11806
|
attributes.createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
11730
11807
|
attributes.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
11731
11808
|
}
|
|
11732
11809
|
const attributesWithDefaults = this.applyDefaults(attributes);
|
|
11733
|
-
const completeData = { id, ...attributesWithDefaults };
|
|
11810
|
+
const completeData = { id: id$1, ...attributesWithDefaults };
|
|
11734
11811
|
const preProcessedData = await this.executeHooks("beforeInsert", completeData);
|
|
11735
11812
|
const extraProps = Object.keys(preProcessedData).filter(
|
|
11736
11813
|
(k) => !(k in completeData) || preProcessedData[k] !== completeData[k]
|
|
@@ -11754,7 +11831,14 @@ ${errorDetails}`,
|
|
|
11754
11831
|
}
|
|
11755
11832
|
const { id: validatedId, ...validatedAttributes } = validated;
|
|
11756
11833
|
Object.assign(validatedAttributes, extraData);
|
|
11757
|
-
|
|
11834
|
+
let finalId = validatedId || id$1;
|
|
11835
|
+
if (!finalId) {
|
|
11836
|
+
finalId = this.idGenerator();
|
|
11837
|
+
if (!finalId || finalId.trim() === "") {
|
|
11838
|
+
const { idGenerator } = await Promise.resolve().then(function () { return id; });
|
|
11839
|
+
finalId = idGenerator();
|
|
11840
|
+
}
|
|
11841
|
+
}
|
|
11758
11842
|
const mappedData = await this.schema.mapper(validatedAttributes);
|
|
11759
11843
|
mappedData._v = String(this.version);
|
|
11760
11844
|
const behaviorImpl = getBehavior(this.behavior);
|
|
@@ -11799,26 +11883,11 @@ ${errorDetails}`,
|
|
|
11799
11883
|
errPut.excess = excess;
|
|
11800
11884
|
throw new ResourceError("metadata headers exceed", { resourceName: this.name, operation: "insert", id: finalId, totalSize, effectiveLimit, excess, suggestion: "Reduce metadata size or number of fields." });
|
|
11801
11885
|
}
|
|
11802
|
-
throw
|
|
11803
|
-
bucket: this.client.config.bucket,
|
|
11804
|
-
key,
|
|
11805
|
-
resourceName: this.name,
|
|
11806
|
-
operation: "insert",
|
|
11807
|
-
id: finalId
|
|
11808
|
-
});
|
|
11886
|
+
throw errPut;
|
|
11809
11887
|
}
|
|
11810
|
-
|
|
11811
|
-
|
|
11812
|
-
|
|
11813
|
-
body,
|
|
11814
|
-
behavior: this.behavior
|
|
11815
|
-
});
|
|
11816
|
-
const finalResult = await this.executeHooks("afterInsert", insertedData);
|
|
11817
|
-
this.emit("insert", {
|
|
11818
|
-
...insertedData,
|
|
11819
|
-
$before: { ...completeData },
|
|
11820
|
-
$after: { ...finalResult }
|
|
11821
|
-
});
|
|
11888
|
+
const insertedObject = await this.get(finalId);
|
|
11889
|
+
const finalResult = await this.executeHooks("afterInsert", insertedObject);
|
|
11890
|
+
this.emit("insert", finalResult);
|
|
11822
11891
|
return finalResult;
|
|
11823
11892
|
}
|
|
11824
11893
|
/**
|
|
@@ -11827,7 +11896,7 @@ ${errorDetails}`,
|
|
|
11827
11896
|
* @returns {Promise<Object>} The resource object with all attributes and metadata
|
|
11828
11897
|
* @example
|
|
11829
11898
|
* const user = await resource.get('user-123');
|
|
11830
|
-
|
|
11899
|
+
*/
|
|
11831
11900
|
async get(id) {
|
|
11832
11901
|
if (isObject$1(id)) throw new Error(`id cannot be an object`);
|
|
11833
11902
|
if (isEmpty(id)) throw new Error("id cannot be empty");
|
|
@@ -11842,17 +11911,6 @@ ${errorDetails}`,
|
|
|
11842
11911
|
id
|
|
11843
11912
|
});
|
|
11844
11913
|
}
|
|
11845
|
-
if (request.ContentLength === 0) {
|
|
11846
|
-
const noContentErr = new Error(`No such key: ${key} [bucket:${this.client.config.bucket}]`);
|
|
11847
|
-
noContentErr.name = "NoSuchKey";
|
|
11848
|
-
throw mapAwsError(noContentErr, {
|
|
11849
|
-
bucket: this.client.config.bucket,
|
|
11850
|
-
key,
|
|
11851
|
-
resourceName: this.name,
|
|
11852
|
-
operation: "get",
|
|
11853
|
-
id
|
|
11854
|
-
});
|
|
11855
|
-
}
|
|
11856
11914
|
const objectVersionRaw = request.Metadata?._v || this.version;
|
|
11857
11915
|
const objectVersion = typeof objectVersionRaw === "string" && objectVersionRaw.startsWith("v") ? objectVersionRaw.slice(1) : objectVersionRaw;
|
|
11858
11916
|
const schema = await this.getSchemaForVersion(objectVersion);
|
|
@@ -13191,6 +13249,18 @@ ${errorDetails}`,
|
|
|
13191
13249
|
});
|
|
13192
13250
|
return result2;
|
|
13193
13251
|
}
|
|
13252
|
+
if (behavior === "user-managed" && body && body.trim() !== "") {
|
|
13253
|
+
const [okBody, errBody, parsedBody] = await try_fn_default(() => Promise.resolve(JSON.parse(body)));
|
|
13254
|
+
if (okBody) {
|
|
13255
|
+
const [okUnmap, errUnmap, unmappedBody] = await try_fn_default(() => this.schema.unmapper(parsedBody));
|
|
13256
|
+
const bodyData = okUnmap ? unmappedBody : {};
|
|
13257
|
+
const merged = { ...bodyData, ...unmappedMetadata, id };
|
|
13258
|
+
Object.keys(merged).forEach((k) => {
|
|
13259
|
+
merged[k] = fixValue(merged[k]);
|
|
13260
|
+
});
|
|
13261
|
+
return filterInternalFields(merged);
|
|
13262
|
+
}
|
|
13263
|
+
}
|
|
13194
13264
|
const result = { ...unmappedMetadata, id };
|
|
13195
13265
|
Object.keys(result).forEach((k) => {
|
|
13196
13266
|
result[k] = fixValue(result[k]);
|
|
@@ -13433,7 +13503,7 @@ class Database extends EventEmitter {
|
|
|
13433
13503
|
this.id = idGenerator(7);
|
|
13434
13504
|
this.version = "1";
|
|
13435
13505
|
this.s3dbVersion = (() => {
|
|
13436
|
-
const [ok, err, version] = try_fn_default(() => true ? "8.1.
|
|
13506
|
+
const [ok, err, version] = try_fn_default(() => true ? "8.1.1" : "latest");
|
|
13437
13507
|
return ok ? version : "latest";
|
|
13438
13508
|
})();
|
|
13439
13509
|
this.resources = {};
|