s3db.js 4.1.14 → 5.1.0
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 +472 -247
- package/dist/s3db.cjs.min.js +11 -11
- package/dist/s3db.es.js +459 -246
- package/dist/s3db.es.min.js +13 -13
- package/dist/s3db.iife.js +472 -247
- package/dist/s3db.iife.min.js +11 -11
- package/package.json +1 -1
package/dist/s3db.cjs.js
CHANGED
|
@@ -247,7 +247,7 @@ var substr = 'ab'.substr(-1) === 'b' ?
|
|
|
247
247
|
|
|
248
248
|
const idGenerator = nanoid.customAlphabet(nanoid.urlAlphabet, 22);
|
|
249
249
|
const passwordAlphabet = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789";
|
|
250
|
-
nanoid.customAlphabet(passwordAlphabet, 12);
|
|
250
|
+
const passwordGenerator = nanoid.customAlphabet(passwordAlphabet, 12);
|
|
251
251
|
|
|
252
252
|
var domain;
|
|
253
253
|
|
|
@@ -3392,7 +3392,7 @@ function requireJsonStableStringify () {
|
|
|
3392
3392
|
var jsonStableStringifyExports = requireJsonStableStringify();
|
|
3393
3393
|
var jsonStableStringify = /*@__PURE__*/getDefaultExportFromCjs(jsonStableStringifyExports);
|
|
3394
3394
|
|
|
3395
|
-
async function
|
|
3395
|
+
async function secretHandler(actual, errors, schema) {
|
|
3396
3396
|
if (!this.passphrase) {
|
|
3397
3397
|
errors.push({ actual, type: "encryptionKeyMissing" });
|
|
3398
3398
|
return actual;
|
|
@@ -3405,6 +3405,10 @@ async function custom(actual, errors, schema) {
|
|
|
3405
3405
|
}
|
|
3406
3406
|
return actual;
|
|
3407
3407
|
}
|
|
3408
|
+
async function jsonHandler(actual, errors, schema) {
|
|
3409
|
+
if (lodashEs.isString(actual)) return actual;
|
|
3410
|
+
return JSON.stringify(actual);
|
|
3411
|
+
}
|
|
3408
3412
|
class Validator extends FastestValidator {
|
|
3409
3413
|
constructor({ options, passphrase, autoEncrypt = true } = {}) {
|
|
3410
3414
|
super(lodashEs.merge({}, {
|
|
@@ -3426,7 +3430,7 @@ class Validator extends FastestValidator {
|
|
|
3426
3430
|
this.autoEncrypt = autoEncrypt;
|
|
3427
3431
|
this.alias("secret", {
|
|
3428
3432
|
type: "string",
|
|
3429
|
-
custom: this.autoEncrypt ?
|
|
3433
|
+
custom: this.autoEncrypt ? secretHandler : void 0,
|
|
3430
3434
|
messages: {
|
|
3431
3435
|
string: "The '{field}' field must be a string.",
|
|
3432
3436
|
stringMin: "This secret '{field}' field length must be at least {expected} long."
|
|
@@ -3434,11 +3438,15 @@ class Validator extends FastestValidator {
|
|
|
3434
3438
|
});
|
|
3435
3439
|
this.alias("secretAny", {
|
|
3436
3440
|
type: "any",
|
|
3437
|
-
custom: this.autoEncrypt ?
|
|
3441
|
+
custom: this.autoEncrypt ? secretHandler : void 0
|
|
3438
3442
|
});
|
|
3439
3443
|
this.alias("secretNumber", {
|
|
3440
3444
|
type: "number",
|
|
3441
|
-
custom: this.autoEncrypt ?
|
|
3445
|
+
custom: this.autoEncrypt ? secretHandler : void 0
|
|
3446
|
+
});
|
|
3447
|
+
this.alias("json", {
|
|
3448
|
+
type: "any",
|
|
3449
|
+
custom: this.autoEncrypt ? jsonHandler : void 0
|
|
3442
3450
|
});
|
|
3443
3451
|
}
|
|
3444
3452
|
}
|
|
@@ -3450,10 +3458,31 @@ const ValidatorManager = new Proxy(Validator, {
|
|
|
3450
3458
|
}
|
|
3451
3459
|
});
|
|
3452
3460
|
|
|
3461
|
+
function toBase36(num) {
|
|
3462
|
+
return num.toString(36);
|
|
3463
|
+
}
|
|
3464
|
+
function generateBase36Mapping(keys) {
|
|
3465
|
+
const mapping = {};
|
|
3466
|
+
const reversedMapping = {};
|
|
3467
|
+
keys.forEach((key, index) => {
|
|
3468
|
+
const base36Key = toBase36(index);
|
|
3469
|
+
mapping[key] = base36Key;
|
|
3470
|
+
reversedMapping[base36Key] = key;
|
|
3471
|
+
});
|
|
3472
|
+
return { mapping, reversedMapping };
|
|
3473
|
+
}
|
|
3453
3474
|
const SchemaActions = {
|
|
3454
3475
|
trim: (value) => value.trim(),
|
|
3455
3476
|
encrypt: (value, { passphrase }) => encrypt(value, passphrase),
|
|
3456
|
-
decrypt: (value, { passphrase }) =>
|
|
3477
|
+
decrypt: async (value, { passphrase }) => {
|
|
3478
|
+
try {
|
|
3479
|
+
const raw = await decrypt(value, passphrase);
|
|
3480
|
+
return raw;
|
|
3481
|
+
} catch (error) {
|
|
3482
|
+
console.warn(`Schema decrypt error: ${error}`, error);
|
|
3483
|
+
return value;
|
|
3484
|
+
}
|
|
3485
|
+
},
|
|
3457
3486
|
toString: (value) => String(value),
|
|
3458
3487
|
fromArray: (value, { separator }) => {
|
|
3459
3488
|
if (value === null || value === void 0 || !Array.isArray(value)) {
|
|
@@ -3544,8 +3573,9 @@ class Schema {
|
|
|
3544
3573
|
const leafKeys = Object.keys(flatAttrs).filter((k) => !k.includes("$$"));
|
|
3545
3574
|
const objectKeys = this.extractObjectKeys(this.attributes);
|
|
3546
3575
|
const allKeys = [.../* @__PURE__ */ new Set([...leafKeys, ...objectKeys])];
|
|
3547
|
-
|
|
3548
|
-
this.map =
|
|
3576
|
+
const { mapping, reversedMapping } = generateBase36Mapping(allKeys);
|
|
3577
|
+
this.map = mapping;
|
|
3578
|
+
this.reversedMap = reversedMapping;
|
|
3549
3579
|
}
|
|
3550
3580
|
}
|
|
3551
3581
|
defaultOptions() {
|
|
@@ -3586,24 +3616,27 @@ class Schema {
|
|
|
3586
3616
|
if (definition.includes("array")) {
|
|
3587
3617
|
this.addHook("beforeMap", name, "fromArray");
|
|
3588
3618
|
this.addHook("afterUnmap", name, "toArray");
|
|
3589
|
-
}
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
}
|
|
3594
|
-
if (this.options.autoDecrypt) {
|
|
3595
|
-
this.addHook("afterUnmap", name, "decrypt");
|
|
3596
|
-
}
|
|
3597
|
-
}
|
|
3598
|
-
if (definition.includes("number")) {
|
|
3599
|
-
this.addHook("beforeMap", name, "toString");
|
|
3600
|
-
this.addHook("afterUnmap", name, "toNumber");
|
|
3619
|
+
}
|
|
3620
|
+
if (definition.includes("secret")) {
|
|
3621
|
+
if (this.options.autoEncrypt) {
|
|
3622
|
+
this.addHook("beforeMap", name, "encrypt");
|
|
3601
3623
|
}
|
|
3602
|
-
if (
|
|
3603
|
-
this.addHook("
|
|
3604
|
-
this.addHook("afterUnmap", name, "toBool");
|
|
3624
|
+
if (this.options.autoDecrypt) {
|
|
3625
|
+
this.addHook("afterUnmap", name, "decrypt");
|
|
3605
3626
|
}
|
|
3606
3627
|
}
|
|
3628
|
+
if (definition.includes("number")) {
|
|
3629
|
+
this.addHook("beforeMap", name, "toString");
|
|
3630
|
+
this.addHook("afterUnmap", name, "toNumber");
|
|
3631
|
+
}
|
|
3632
|
+
if (definition.includes("boolean")) {
|
|
3633
|
+
this.addHook("beforeMap", name, "fromBool");
|
|
3634
|
+
this.addHook("afterUnmap", name, "toBool");
|
|
3635
|
+
}
|
|
3636
|
+
if (definition.includes("json")) {
|
|
3637
|
+
this.addHook("beforeMap", name, "toJSON");
|
|
3638
|
+
this.addHook("afterUnmap", name, "fromJSON");
|
|
3639
|
+
}
|
|
3607
3640
|
}
|
|
3608
3641
|
}
|
|
3609
3642
|
static import(data) {
|
|
@@ -8618,7 +8651,6 @@ function calculateUTF8Bytes(str) {
|
|
|
8618
8651
|
function calculateAttributeNamesSize(mappedObject) {
|
|
8619
8652
|
let totalSize = 0;
|
|
8620
8653
|
for (const key of Object.keys(mappedObject)) {
|
|
8621
|
-
if (key === "_v") continue;
|
|
8622
8654
|
totalSize += calculateUTF8Bytes(key);
|
|
8623
8655
|
}
|
|
8624
8656
|
return totalSize;
|
|
@@ -8662,6 +8694,30 @@ function calculateTotalSize(mappedObject) {
|
|
|
8662
8694
|
const namesSize = calculateAttributeNamesSize(mappedObject);
|
|
8663
8695
|
return valueTotal + namesSize;
|
|
8664
8696
|
}
|
|
8697
|
+
function getSizeBreakdown(mappedObject) {
|
|
8698
|
+
const valueSizes = calculateAttributeSizes(mappedObject);
|
|
8699
|
+
const namesSize = calculateAttributeNamesSize(mappedObject);
|
|
8700
|
+
const valueTotal = Object.values(valueSizes).reduce((sum, size) => sum + size, 0);
|
|
8701
|
+
const total = valueTotal + namesSize;
|
|
8702
|
+
const sortedAttributes = Object.entries(valueSizes).sort(([, a], [, b]) => b - a).map(([key, size]) => ({
|
|
8703
|
+
attribute: key,
|
|
8704
|
+
size,
|
|
8705
|
+
percentage: (size / total * 100).toFixed(2) + "%"
|
|
8706
|
+
}));
|
|
8707
|
+
return {
|
|
8708
|
+
total,
|
|
8709
|
+
valueSizes,
|
|
8710
|
+
namesSize,
|
|
8711
|
+
valueTotal,
|
|
8712
|
+
breakdown: sortedAttributes,
|
|
8713
|
+
// Add detailed breakdown including names
|
|
8714
|
+
detailedBreakdown: {
|
|
8715
|
+
values: valueTotal,
|
|
8716
|
+
names: namesSize,
|
|
8717
|
+
total
|
|
8718
|
+
}
|
|
8719
|
+
};
|
|
8720
|
+
}
|
|
8665
8721
|
|
|
8666
8722
|
const S3_METADATA_LIMIT_BYTES = 2048;
|
|
8667
8723
|
async function handleInsert$3({ resource, data, mappedData }) {
|
|
@@ -8882,6 +8938,7 @@ function getBehavior(behaviorName) {
|
|
|
8882
8938
|
}
|
|
8883
8939
|
return behavior;
|
|
8884
8940
|
}
|
|
8941
|
+
const AVAILABLE_BEHAVIORS = Object.keys(behaviors);
|
|
8885
8942
|
const DEFAULT_BEHAVIOR = "user-management";
|
|
8886
8943
|
|
|
8887
8944
|
class Resource extends EventEmitter {
|
|
@@ -8951,17 +9008,8 @@ ${validation.errors.join("\n")}`);
|
|
|
8951
9008
|
partitions = {},
|
|
8952
9009
|
paranoid = true,
|
|
8953
9010
|
allNestedObjectsOptional = true,
|
|
8954
|
-
hooks = {}
|
|
8955
|
-
options = {}
|
|
9011
|
+
hooks = {}
|
|
8956
9012
|
} = config;
|
|
8957
|
-
const mergedOptions = {
|
|
8958
|
-
cache: typeof options.cache === "boolean" ? options.cache : cache,
|
|
8959
|
-
autoDecrypt: typeof options.autoDecrypt === "boolean" ? options.autoDecrypt : autoDecrypt,
|
|
8960
|
-
timestamps: typeof options.timestamps === "boolean" ? options.timestamps : timestamps,
|
|
8961
|
-
paranoid: typeof options.paranoid === "boolean" ? options.paranoid : paranoid,
|
|
8962
|
-
allNestedObjectsOptional: typeof options.allNestedObjectsOptional === "boolean" ? options.allNestedObjectsOptional : allNestedObjectsOptional,
|
|
8963
|
-
partitions: options.partitions || partitions || {}
|
|
8964
|
-
};
|
|
8965
9013
|
this.name = name;
|
|
8966
9014
|
this.client = client;
|
|
8967
9015
|
this.version = version;
|
|
@@ -8970,13 +9018,13 @@ ${validation.errors.join("\n")}`);
|
|
|
8970
9018
|
this.parallelism = parallelism;
|
|
8971
9019
|
this.passphrase = passphrase ?? "secret";
|
|
8972
9020
|
this.config = {
|
|
8973
|
-
cache
|
|
9021
|
+
cache,
|
|
8974
9022
|
hooks,
|
|
8975
|
-
paranoid
|
|
8976
|
-
timestamps
|
|
8977
|
-
partitions
|
|
8978
|
-
autoDecrypt
|
|
8979
|
-
allNestedObjectsOptional
|
|
9023
|
+
paranoid,
|
|
9024
|
+
timestamps,
|
|
9025
|
+
partitions,
|
|
9026
|
+
autoDecrypt,
|
|
9027
|
+
allNestedObjectsOptional
|
|
8980
9028
|
};
|
|
8981
9029
|
this.hooks = {
|
|
8982
9030
|
preInsert: [],
|
|
@@ -8987,39 +9035,7 @@ ${validation.errors.join("\n")}`);
|
|
|
8987
9035
|
afterDelete: []
|
|
8988
9036
|
};
|
|
8989
9037
|
this.attributes = attributes || {};
|
|
8990
|
-
|
|
8991
|
-
this.attributes.createdAt = "string|optional";
|
|
8992
|
-
this.attributes.updatedAt = "string|optional";
|
|
8993
|
-
if (!this.config.partitions) {
|
|
8994
|
-
this.config.partitions = {};
|
|
8995
|
-
}
|
|
8996
|
-
if (!this.config.partitions.byCreatedDate) {
|
|
8997
|
-
this.config.partitions.byCreatedDate = {
|
|
8998
|
-
fields: {
|
|
8999
|
-
createdAt: "date|maxlength:10"
|
|
9000
|
-
}
|
|
9001
|
-
};
|
|
9002
|
-
}
|
|
9003
|
-
if (!this.config.partitions.byUpdatedDate) {
|
|
9004
|
-
this.config.partitions.byUpdatedDate = {
|
|
9005
|
-
fields: {
|
|
9006
|
-
updatedAt: "date|maxlength:10"
|
|
9007
|
-
}
|
|
9008
|
-
};
|
|
9009
|
-
}
|
|
9010
|
-
}
|
|
9011
|
-
this.schema = new Schema({
|
|
9012
|
-
name,
|
|
9013
|
-
attributes: this.attributes,
|
|
9014
|
-
passphrase,
|
|
9015
|
-
version: this.version,
|
|
9016
|
-
options: {
|
|
9017
|
-
autoDecrypt: this.config.autoDecrypt,
|
|
9018
|
-
allNestedObjectsOptional: this.config.allNestedObjectsOptional
|
|
9019
|
-
}
|
|
9020
|
-
});
|
|
9021
|
-
this.setupPartitionHooks();
|
|
9022
|
-
this.validatePartitions();
|
|
9038
|
+
this.applyConfiguration();
|
|
9023
9039
|
if (hooks) {
|
|
9024
9040
|
for (const [event, hooksArr] of Object.entries(hooks)) {
|
|
9025
9041
|
if (Array.isArray(hooksArr) && this.hooks[event]) {
|
|
@@ -9058,15 +9074,17 @@ ${validation.errors.join("\n")}`);
|
|
|
9058
9074
|
return exported;
|
|
9059
9075
|
}
|
|
9060
9076
|
/**
|
|
9061
|
-
*
|
|
9062
|
-
*
|
|
9077
|
+
* Apply configuration settings (timestamps, partitions, hooks)
|
|
9078
|
+
* This method ensures that all configuration-dependent features are properly set up
|
|
9063
9079
|
*/
|
|
9064
|
-
|
|
9065
|
-
const oldAttributes = this.attributes;
|
|
9066
|
-
this.attributes = newAttributes;
|
|
9080
|
+
applyConfiguration() {
|
|
9067
9081
|
if (this.config.timestamps) {
|
|
9068
|
-
|
|
9069
|
-
|
|
9082
|
+
if (!this.attributes.createdAt) {
|
|
9083
|
+
this.attributes.createdAt = "string|optional";
|
|
9084
|
+
}
|
|
9085
|
+
if (!this.attributes.updatedAt) {
|
|
9086
|
+
this.attributes.updatedAt = "string|optional";
|
|
9087
|
+
}
|
|
9070
9088
|
if (!this.config.partitions) {
|
|
9071
9089
|
this.config.partitions = {};
|
|
9072
9090
|
}
|
|
@@ -9085,9 +9103,10 @@ ${validation.errors.join("\n")}`);
|
|
|
9085
9103
|
};
|
|
9086
9104
|
}
|
|
9087
9105
|
}
|
|
9106
|
+
this.setupPartitionHooks();
|
|
9088
9107
|
this.schema = new Schema({
|
|
9089
9108
|
name: this.name,
|
|
9090
|
-
attributes:
|
|
9109
|
+
attributes: this.attributes,
|
|
9091
9110
|
passphrase: this.passphrase,
|
|
9092
9111
|
version: this.version,
|
|
9093
9112
|
options: {
|
|
@@ -9095,8 +9114,16 @@ ${validation.errors.join("\n")}`);
|
|
|
9095
9114
|
allNestedObjectsOptional: this.config.allNestedObjectsOptional
|
|
9096
9115
|
}
|
|
9097
9116
|
});
|
|
9098
|
-
this.setupPartitionHooks();
|
|
9099
9117
|
this.validatePartitions();
|
|
9118
|
+
}
|
|
9119
|
+
/**
|
|
9120
|
+
* Update resource attributes and rebuild schema
|
|
9121
|
+
* @param {Object} newAttributes - New attributes definition
|
|
9122
|
+
*/
|
|
9123
|
+
updateAttributes(newAttributes) {
|
|
9124
|
+
const oldAttributes = this.attributes;
|
|
9125
|
+
this.attributes = newAttributes;
|
|
9126
|
+
this.applyConfiguration();
|
|
9100
9127
|
return { oldAttributes, newAttributes };
|
|
9101
9128
|
}
|
|
9102
9129
|
/**
|
|
@@ -9184,7 +9211,7 @@ ${validation.errors.join("\n")}`);
|
|
|
9184
9211
|
for (const fieldName of Object.keys(partitionDef.fields)) {
|
|
9185
9212
|
if (!this.fieldExistsInAttributes(fieldName)) {
|
|
9186
9213
|
throw new Error(
|
|
9187
|
-
`Partition '${partitionName}' uses field '${fieldName}' which does not exist in resource
|
|
9214
|
+
`Partition '${partitionName}' uses field '${fieldName}' which does not exist in resource attributes. Available fields: ${currentAttributes.join(", ")}.`
|
|
9188
9215
|
);
|
|
9189
9216
|
}
|
|
9190
9217
|
}
|
|
@@ -9297,7 +9324,11 @@ ${validation.errors.join("\n")}`);
|
|
|
9297
9324
|
if (partitionSegments.length === 0) {
|
|
9298
9325
|
return null;
|
|
9299
9326
|
}
|
|
9300
|
-
|
|
9327
|
+
const finalId = id || data?.id;
|
|
9328
|
+
if (!finalId) {
|
|
9329
|
+
return null;
|
|
9330
|
+
}
|
|
9331
|
+
return join(`resource=${this.name}`, `partition=${partitionName}`, ...partitionSegments, `id=${finalId}`);
|
|
9301
9332
|
}
|
|
9302
9333
|
/**
|
|
9303
9334
|
* Get nested field value from data object using dot notation
|
|
@@ -9526,12 +9557,12 @@ ${validation.errors.join("\n")}`);
|
|
|
9526
9557
|
* console.log(updatedUser.updatedAt); // ISO timestamp
|
|
9527
9558
|
*/
|
|
9528
9559
|
async update(id, attributes) {
|
|
9529
|
-
const
|
|
9560
|
+
const originalData = await this.get(id);
|
|
9530
9561
|
if (this.config.timestamps) {
|
|
9531
9562
|
attributes.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9532
9563
|
}
|
|
9533
9564
|
const preProcessedData = await this.executeHooks("preUpdate", attributes);
|
|
9534
|
-
const attrs = lodashEs.merge(
|
|
9565
|
+
const attrs = lodashEs.merge(originalData, preProcessedData);
|
|
9535
9566
|
delete attrs.id;
|
|
9536
9567
|
const { isValid, errors, data: validated } = await this.validate(attrs);
|
|
9537
9568
|
if (!isValid) {
|
|
@@ -9542,6 +9573,9 @@ ${validation.errors.join("\n")}`);
|
|
|
9542
9573
|
validation: errors
|
|
9543
9574
|
});
|
|
9544
9575
|
}
|
|
9576
|
+
const oldData = { ...originalData, id };
|
|
9577
|
+
const newData = { ...validated, id };
|
|
9578
|
+
await this.handlePartitionReferenceUpdates(oldData, newData);
|
|
9545
9579
|
const mappedData = await this.schema.mapper(validated);
|
|
9546
9580
|
const behaviorImpl = getBehavior(this.behavior);
|
|
9547
9581
|
const { mappedData: processedMetadata, body } = await behaviorImpl.handleUpdate({
|
|
@@ -9577,7 +9611,6 @@ ${validation.errors.join("\n")}`);
|
|
|
9577
9611
|
});
|
|
9578
9612
|
validated.id = id;
|
|
9579
9613
|
await this.executeHooks("afterUpdate", validated);
|
|
9580
|
-
await this.updatePartitionReferences(validated);
|
|
9581
9614
|
this.emit("update", preProcessedData, validated);
|
|
9582
9615
|
return validated;
|
|
9583
9616
|
}
|
|
@@ -9864,23 +9897,104 @@ ${validation.errors.join("\n")}`);
|
|
|
9864
9897
|
* });
|
|
9865
9898
|
*/
|
|
9866
9899
|
async list({ partition = null, partitionValues = {}, limit, offset = 0 } = {}) {
|
|
9867
|
-
|
|
9868
|
-
|
|
9869
|
-
|
|
9900
|
+
try {
|
|
9901
|
+
if (!partition) {
|
|
9902
|
+
let ids2 = [];
|
|
9903
|
+
try {
|
|
9904
|
+
ids2 = await this.listIds({ partition, partitionValues });
|
|
9905
|
+
} catch (listIdsError) {
|
|
9906
|
+
console.warn(`Failed to get list IDs:`, listIdsError.message);
|
|
9907
|
+
return [];
|
|
9908
|
+
}
|
|
9909
|
+
let filteredIds2 = ids2.slice(offset);
|
|
9910
|
+
if (limit) {
|
|
9911
|
+
filteredIds2 = filteredIds2.slice(0, limit);
|
|
9912
|
+
}
|
|
9913
|
+
const { results: results2, errors: errors2 } = await promisePool.PromisePool.for(filteredIds2).withConcurrency(this.parallelism).handleError(async (error, id) => {
|
|
9914
|
+
console.warn(`Failed to get resource ${id}:`, error.message);
|
|
9915
|
+
return null;
|
|
9916
|
+
}).process(async (id) => {
|
|
9917
|
+
try {
|
|
9918
|
+
return await this.get(id);
|
|
9919
|
+
} catch (error) {
|
|
9920
|
+
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9921
|
+
console.warn(`Decryption failed for ${id}, returning basic info`);
|
|
9922
|
+
return {
|
|
9923
|
+
id,
|
|
9924
|
+
_decryptionFailed: true,
|
|
9925
|
+
_error: error.message
|
|
9926
|
+
};
|
|
9927
|
+
}
|
|
9928
|
+
throw error;
|
|
9929
|
+
}
|
|
9930
|
+
});
|
|
9931
|
+
const validResults2 = results2.filter((item) => item !== null);
|
|
9932
|
+
this.emit("list", { partition, partitionValues, count: validResults2.length, errors: errors2.length });
|
|
9933
|
+
return validResults2;
|
|
9934
|
+
}
|
|
9935
|
+
if (!this.config.partitions || !this.config.partitions[partition]) {
|
|
9936
|
+
console.warn(`Partition '${partition}' not found in resource '${this.name}'`);
|
|
9937
|
+
this.emit("list", { partition, partitionValues, count: 0, errors: 0 });
|
|
9938
|
+
return [];
|
|
9939
|
+
}
|
|
9940
|
+
const partitionDef = this.config.partitions[partition];
|
|
9941
|
+
const partitionSegments = [];
|
|
9942
|
+
const sortedFields = Object.entries(partitionDef.fields).sort(([a], [b]) => a.localeCompare(b));
|
|
9943
|
+
for (const [fieldName, rule] of sortedFields) {
|
|
9944
|
+
const value = partitionValues[fieldName];
|
|
9945
|
+
if (value !== void 0 && value !== null) {
|
|
9946
|
+
const transformedValue = this.applyPartitionRule(value, rule);
|
|
9947
|
+
partitionSegments.push(`${fieldName}=${transformedValue}`);
|
|
9948
|
+
}
|
|
9949
|
+
}
|
|
9950
|
+
let prefix;
|
|
9951
|
+
if (partitionSegments.length > 0) {
|
|
9952
|
+
prefix = `resource=${this.name}/partition=${partition}/${partitionSegments.join("/")}`;
|
|
9953
|
+
} else {
|
|
9954
|
+
prefix = `resource=${this.name}/partition=${partition}`;
|
|
9955
|
+
}
|
|
9956
|
+
let keys = [];
|
|
9957
|
+
try {
|
|
9958
|
+
keys = await this.client.getAllKeys({ prefix });
|
|
9959
|
+
} catch (getKeysError) {
|
|
9960
|
+
console.warn(`Failed to get partition keys:`, getKeysError.message);
|
|
9961
|
+
return [];
|
|
9962
|
+
}
|
|
9963
|
+
const ids = keys.map((key) => {
|
|
9964
|
+
const parts = key.split("/");
|
|
9965
|
+
const idPart = parts.find((part) => part.startsWith("id="));
|
|
9966
|
+
return idPart ? idPart.replace("id=", "") : null;
|
|
9967
|
+
}).filter(Boolean);
|
|
9968
|
+
let filteredIds = ids.slice(offset);
|
|
9870
9969
|
if (limit) {
|
|
9871
|
-
|
|
9970
|
+
filteredIds = filteredIds.slice(0, limit);
|
|
9872
9971
|
}
|
|
9873
|
-
const { results
|
|
9874
|
-
console.warn(`Failed to get resource ${id}:`, error.message);
|
|
9972
|
+
const { results, errors } = await promisePool.PromisePool.for(filteredIds).withConcurrency(this.parallelism).handleError(async (error, id) => {
|
|
9973
|
+
console.warn(`Failed to get partition resource ${id}:`, error.message);
|
|
9875
9974
|
return null;
|
|
9876
9975
|
}).process(async (id) => {
|
|
9877
9976
|
try {
|
|
9878
|
-
|
|
9977
|
+
const keyForId = keys.find((key) => key.includes(`id=${id}`));
|
|
9978
|
+
if (!keyForId) {
|
|
9979
|
+
throw new Error(`Partition key not found for ID ${id}`);
|
|
9980
|
+
}
|
|
9981
|
+
const keyParts = keyForId.split("/");
|
|
9982
|
+
const actualPartitionValues = {};
|
|
9983
|
+
for (const [fieldName, rule] of sortedFields) {
|
|
9984
|
+
const fieldPart = keyParts.find((part) => part.startsWith(`${fieldName}=`));
|
|
9985
|
+
if (fieldPart) {
|
|
9986
|
+
const value = fieldPart.replace(`${fieldName}=`, "");
|
|
9987
|
+
actualPartitionValues[fieldName] = value;
|
|
9988
|
+
}
|
|
9989
|
+
}
|
|
9990
|
+
return await this.getFromPartition({ id, partitionName: partition, partitionValues: actualPartitionValues });
|
|
9879
9991
|
} catch (error) {
|
|
9880
9992
|
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9881
|
-
console.warn(`Decryption failed for ${id}, returning basic info`);
|
|
9993
|
+
console.warn(`Decryption failed for partition resource ${id}, returning basic info`);
|
|
9882
9994
|
return {
|
|
9883
9995
|
id,
|
|
9996
|
+
_partition: partition,
|
|
9997
|
+
_partitionValues: partitionValues,
|
|
9884
9998
|
_decryptionFailed: true,
|
|
9885
9999
|
_error: error.message
|
|
9886
10000
|
};
|
|
@@ -9888,62 +10002,19 @@ ${validation.errors.join("\n")}`);
|
|
|
9888
10002
|
throw error;
|
|
9889
10003
|
}
|
|
9890
10004
|
});
|
|
9891
|
-
const
|
|
9892
|
-
this.emit("list", { partition, partitionValues, count:
|
|
9893
|
-
return
|
|
9894
|
-
}
|
|
9895
|
-
|
|
9896
|
-
|
|
9897
|
-
|
|
9898
|
-
|
|
9899
|
-
const partitionSegments = [];
|
|
9900
|
-
const sortedFields = Object.entries(partitionDef.fields).sort(([a], [b]) => a.localeCompare(b));
|
|
9901
|
-
for (const [fieldName, rule] of sortedFields) {
|
|
9902
|
-
const value = partitionValues[fieldName];
|
|
9903
|
-
if (value !== void 0 && value !== null) {
|
|
9904
|
-
const transformedValue = this.applyPartitionRule(value, rule);
|
|
9905
|
-
partitionSegments.push(`${fieldName}=${transformedValue}`);
|
|
10005
|
+
const validResults = results.filter((item) => item !== null);
|
|
10006
|
+
this.emit("list", { partition, partitionValues, count: validResults.length, errors: errors.length });
|
|
10007
|
+
return validResults;
|
|
10008
|
+
} catch (error) {
|
|
10009
|
+
if (error.message.includes("Partition '") && error.message.includes("' not found")) {
|
|
10010
|
+
console.warn(`Partition error in list method:`, error.message);
|
|
10011
|
+
this.emit("list", { partition, partitionValues, count: 0, errors: 1 });
|
|
10012
|
+
return [];
|
|
9906
10013
|
}
|
|
10014
|
+
console.error(`Critical error in list method:`, error.message);
|
|
10015
|
+
this.emit("list", { partition, partitionValues, count: 0, errors: 1 });
|
|
10016
|
+
return [];
|
|
9907
10017
|
}
|
|
9908
|
-
let prefix;
|
|
9909
|
-
if (partitionSegments.length > 0) {
|
|
9910
|
-
prefix = `resource=${this.name}/partition=${partition}/${partitionSegments.join("/")}`;
|
|
9911
|
-
} else {
|
|
9912
|
-
prefix = `resource=${this.name}/partition=${partition}`;
|
|
9913
|
-
}
|
|
9914
|
-
const keys = await this.client.getAllKeys({ prefix });
|
|
9915
|
-
const ids = keys.map((key) => {
|
|
9916
|
-
const parts = key.split("/");
|
|
9917
|
-
const idPart = parts.find((part) => part.startsWith("id="));
|
|
9918
|
-
return idPart ? idPart.replace("id=", "") : null;
|
|
9919
|
-
}).filter(Boolean);
|
|
9920
|
-
let filteredIds = ids.slice(offset);
|
|
9921
|
-
if (limit) {
|
|
9922
|
-
filteredIds = filteredIds.slice(0, limit);
|
|
9923
|
-
}
|
|
9924
|
-
const { results, errors } = await promisePool.PromisePool.for(filteredIds).withConcurrency(this.parallelism).handleError(async (error, id) => {
|
|
9925
|
-
console.warn(`Failed to get partition resource ${id}:`, error.message);
|
|
9926
|
-
return null;
|
|
9927
|
-
}).process(async (id) => {
|
|
9928
|
-
try {
|
|
9929
|
-
return await this.getFromPartition({ id, partitionName: partition, partitionValues });
|
|
9930
|
-
} catch (error) {
|
|
9931
|
-
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9932
|
-
console.warn(`Decryption failed for partition resource ${id}, returning basic info`);
|
|
9933
|
-
return {
|
|
9934
|
-
id,
|
|
9935
|
-
_partition: partition,
|
|
9936
|
-
_partitionValues: partitionValues,
|
|
9937
|
-
_decryptionFailed: true,
|
|
9938
|
-
_error: error.message
|
|
9939
|
-
};
|
|
9940
|
-
}
|
|
9941
|
-
throw error;
|
|
9942
|
-
}
|
|
9943
|
-
});
|
|
9944
|
-
const validResults = results.filter((item) => item !== null);
|
|
9945
|
-
this.emit("list", { partition, partitionValues, count: validResults.length, errors: errors.length });
|
|
9946
|
-
return validResults;
|
|
9947
10018
|
}
|
|
9948
10019
|
/**
|
|
9949
10020
|
* Get multiple resources by their IDs
|
|
@@ -10050,36 +10121,67 @@ ${validation.errors.join("\n")}`);
|
|
|
10050
10121
|
* console.log(`Got ${fastPage.items.length} items`); // totalItems will be null
|
|
10051
10122
|
*/
|
|
10052
10123
|
async page({ offset = 0, size = 100, partition = null, partitionValues = {}, skipCount = false } = {}) {
|
|
10053
|
-
|
|
10054
|
-
|
|
10055
|
-
|
|
10056
|
-
|
|
10057
|
-
|
|
10058
|
-
|
|
10059
|
-
|
|
10060
|
-
|
|
10061
|
-
|
|
10062
|
-
|
|
10063
|
-
|
|
10064
|
-
|
|
10065
|
-
});
|
|
10066
|
-
const result = {
|
|
10067
|
-
items,
|
|
10068
|
-
totalItems,
|
|
10069
|
-
page,
|
|
10070
|
-
pageSize: size,
|
|
10071
|
-
totalPages,
|
|
10072
|
-
// Add additional metadata for debugging
|
|
10073
|
-
_debug: {
|
|
10074
|
-
requestedSize: size,
|
|
10075
|
-
requestedOffset: offset,
|
|
10076
|
-
actualItemsReturned: items.length,
|
|
10077
|
-
skipCount,
|
|
10078
|
-
hasTotalItems: totalItems !== null
|
|
10124
|
+
try {
|
|
10125
|
+
let totalItems = null;
|
|
10126
|
+
let totalPages = null;
|
|
10127
|
+
if (!skipCount) {
|
|
10128
|
+
try {
|
|
10129
|
+
totalItems = await this.count({ partition, partitionValues });
|
|
10130
|
+
totalPages = Math.ceil(totalItems / size);
|
|
10131
|
+
} catch (countError) {
|
|
10132
|
+
console.warn(`Failed to get count for page:`, countError.message);
|
|
10133
|
+
totalItems = null;
|
|
10134
|
+
totalPages = null;
|
|
10135
|
+
}
|
|
10079
10136
|
}
|
|
10080
|
-
|
|
10081
|
-
|
|
10082
|
-
|
|
10137
|
+
const page = Math.floor(offset / size);
|
|
10138
|
+
let items = [];
|
|
10139
|
+
try {
|
|
10140
|
+
items = await this.list({
|
|
10141
|
+
partition,
|
|
10142
|
+
partitionValues,
|
|
10143
|
+
limit: size,
|
|
10144
|
+
offset
|
|
10145
|
+
});
|
|
10146
|
+
} catch (listError) {
|
|
10147
|
+
console.warn(`Failed to get items for page:`, listError.message);
|
|
10148
|
+
items = [];
|
|
10149
|
+
}
|
|
10150
|
+
const result = {
|
|
10151
|
+
items,
|
|
10152
|
+
totalItems,
|
|
10153
|
+
page,
|
|
10154
|
+
pageSize: size,
|
|
10155
|
+
totalPages,
|
|
10156
|
+
// Add additional metadata for debugging
|
|
10157
|
+
_debug: {
|
|
10158
|
+
requestedSize: size,
|
|
10159
|
+
requestedOffset: offset,
|
|
10160
|
+
actualItemsReturned: items.length,
|
|
10161
|
+
skipCount,
|
|
10162
|
+
hasTotalItems: totalItems !== null
|
|
10163
|
+
}
|
|
10164
|
+
};
|
|
10165
|
+
this.emit("page", result);
|
|
10166
|
+
return result;
|
|
10167
|
+
} catch (error) {
|
|
10168
|
+
console.error(`Critical error in page method:`, error.message);
|
|
10169
|
+
return {
|
|
10170
|
+
items: [],
|
|
10171
|
+
totalItems: null,
|
|
10172
|
+
page: Math.floor(offset / size),
|
|
10173
|
+
pageSize: size,
|
|
10174
|
+
totalPages: null,
|
|
10175
|
+
_debug: {
|
|
10176
|
+
requestedSize: size,
|
|
10177
|
+
requestedOffset: offset,
|
|
10178
|
+
actualItemsReturned: 0,
|
|
10179
|
+
skipCount,
|
|
10180
|
+
hasTotalItems: false,
|
|
10181
|
+
error: error.message
|
|
10182
|
+
}
|
|
10183
|
+
};
|
|
10184
|
+
}
|
|
10083
10185
|
}
|
|
10084
10186
|
readable() {
|
|
10085
10187
|
const stream = new ResourceReader({ resource: this });
|
|
@@ -10372,7 +10474,118 @@ ${validation.errors.join("\n")}`);
|
|
|
10372
10474
|
return results.slice(0, limit);
|
|
10373
10475
|
}
|
|
10374
10476
|
/**
|
|
10375
|
-
*
|
|
10477
|
+
* Handle partition reference updates with change detection
|
|
10478
|
+
* @param {Object} oldData - Original object data before update
|
|
10479
|
+
* @param {Object} newData - Updated object data
|
|
10480
|
+
*/
|
|
10481
|
+
async handlePartitionReferenceUpdates(oldData, newData) {
|
|
10482
|
+
const partitions = this.config.partitions;
|
|
10483
|
+
if (!partitions || Object.keys(partitions).length === 0) {
|
|
10484
|
+
return;
|
|
10485
|
+
}
|
|
10486
|
+
for (const [partitionName, partition] of Object.entries(partitions)) {
|
|
10487
|
+
try {
|
|
10488
|
+
await this.handlePartitionReferenceUpdate(partitionName, partition, oldData, newData);
|
|
10489
|
+
} catch (error) {
|
|
10490
|
+
console.warn(`Failed to update partition references for ${partitionName}:`, error.message);
|
|
10491
|
+
}
|
|
10492
|
+
}
|
|
10493
|
+
const id = newData.id || oldData.id;
|
|
10494
|
+
for (const [partitionName, partition] of Object.entries(partitions)) {
|
|
10495
|
+
const prefix = `resource=${this.name}/partition=${partitionName}`;
|
|
10496
|
+
let allKeys = [];
|
|
10497
|
+
try {
|
|
10498
|
+
allKeys = await this.client.getAllKeys({ prefix });
|
|
10499
|
+
} catch (error) {
|
|
10500
|
+
console.warn(`Aggressive cleanup: could not list keys for partition ${partitionName}:`, error.message);
|
|
10501
|
+
continue;
|
|
10502
|
+
}
|
|
10503
|
+
const validKey = this.getPartitionKey({ partitionName, id, data: newData });
|
|
10504
|
+
for (const key of allKeys) {
|
|
10505
|
+
if (key.endsWith(`/id=${id}`) && key !== validKey) {
|
|
10506
|
+
try {
|
|
10507
|
+
await this.client.deleteObject(key);
|
|
10508
|
+
} catch (error) {
|
|
10509
|
+
console.warn(`Aggressive cleanup: could not delete stale partition key ${key}:`, error.message);
|
|
10510
|
+
}
|
|
10511
|
+
}
|
|
10512
|
+
}
|
|
10513
|
+
}
|
|
10514
|
+
}
|
|
10515
|
+
/**
|
|
10516
|
+
* Handle partition reference update for a specific partition
|
|
10517
|
+
* @param {string} partitionName - Name of the partition
|
|
10518
|
+
* @param {Object} partition - Partition definition
|
|
10519
|
+
* @param {Object} oldData - Original object data before update
|
|
10520
|
+
* @param {Object} newData - Updated object data
|
|
10521
|
+
*/
|
|
10522
|
+
async handlePartitionReferenceUpdate(partitionName, partition, oldData, newData) {
|
|
10523
|
+
const id = newData.id || oldData.id;
|
|
10524
|
+
const oldPartitionKey = this.getPartitionKey({ partitionName, id, data: oldData });
|
|
10525
|
+
const newPartitionKey = this.getPartitionKey({ partitionName, id, data: newData });
|
|
10526
|
+
if (oldPartitionKey !== newPartitionKey) {
|
|
10527
|
+
if (oldPartitionKey) {
|
|
10528
|
+
try {
|
|
10529
|
+
await this.client.deleteObject(oldPartitionKey);
|
|
10530
|
+
} catch (error) {
|
|
10531
|
+
console.warn(`Old partition object could not be deleted for ${partitionName}:`, error.message);
|
|
10532
|
+
}
|
|
10533
|
+
}
|
|
10534
|
+
if (newPartitionKey) {
|
|
10535
|
+
try {
|
|
10536
|
+
const mappedData = await this.schema.mapper(newData);
|
|
10537
|
+
if (mappedData.undefined !== void 0) delete mappedData.undefined;
|
|
10538
|
+
const behaviorImpl = getBehavior(this.behavior);
|
|
10539
|
+
const { mappedData: processedMetadata, body } = await behaviorImpl.handleUpdate({
|
|
10540
|
+
resource: this,
|
|
10541
|
+
id,
|
|
10542
|
+
data: newData,
|
|
10543
|
+
mappedData
|
|
10544
|
+
});
|
|
10545
|
+
if (processedMetadata.undefined !== void 0) delete processedMetadata.undefined;
|
|
10546
|
+
const partitionMetadata = {
|
|
10547
|
+
...processedMetadata,
|
|
10548
|
+
_version: this.version
|
|
10549
|
+
};
|
|
10550
|
+
if (partitionMetadata.undefined !== void 0) delete partitionMetadata.undefined;
|
|
10551
|
+
await this.client.putObject({
|
|
10552
|
+
key: newPartitionKey,
|
|
10553
|
+
metadata: partitionMetadata,
|
|
10554
|
+
body
|
|
10555
|
+
});
|
|
10556
|
+
} catch (error) {
|
|
10557
|
+
console.warn(`New partition object could not be created for ${partitionName}:`, error.message);
|
|
10558
|
+
}
|
|
10559
|
+
}
|
|
10560
|
+
} else if (newPartitionKey) {
|
|
10561
|
+
try {
|
|
10562
|
+
const mappedData = await this.schema.mapper(newData);
|
|
10563
|
+
if (mappedData.undefined !== void 0) delete mappedData.undefined;
|
|
10564
|
+
const behaviorImpl = getBehavior(this.behavior);
|
|
10565
|
+
const { mappedData: processedMetadata, body } = await behaviorImpl.handleUpdate({
|
|
10566
|
+
resource: this,
|
|
10567
|
+
id,
|
|
10568
|
+
data: newData,
|
|
10569
|
+
mappedData
|
|
10570
|
+
});
|
|
10571
|
+
if (processedMetadata.undefined !== void 0) delete processedMetadata.undefined;
|
|
10572
|
+
const partitionMetadata = {
|
|
10573
|
+
...processedMetadata,
|
|
10574
|
+
_version: this.version
|
|
10575
|
+
};
|
|
10576
|
+
if (partitionMetadata.undefined !== void 0) delete partitionMetadata.undefined;
|
|
10577
|
+
await this.client.putObject({
|
|
10578
|
+
key: newPartitionKey,
|
|
10579
|
+
metadata: partitionMetadata,
|
|
10580
|
+
body
|
|
10581
|
+
});
|
|
10582
|
+
} catch (error) {
|
|
10583
|
+
console.warn(`Partition object could not be updated for ${partitionName}:`, error.message);
|
|
10584
|
+
}
|
|
10585
|
+
}
|
|
10586
|
+
}
|
|
10587
|
+
/**
|
|
10588
|
+
* Update partition objects to keep them in sync (legacy method for backward compatibility)
|
|
10376
10589
|
* @param {Object} data - Updated object data
|
|
10377
10590
|
*/
|
|
10378
10591
|
async updatePartitionReferences(data) {
|
|
@@ -10381,6 +10594,10 @@ ${validation.errors.join("\n")}`);
|
|
|
10381
10594
|
return;
|
|
10382
10595
|
}
|
|
10383
10596
|
for (const [partitionName, partition] of Object.entries(partitions)) {
|
|
10597
|
+
if (!partition || !partition.fields || typeof partition.fields !== "object") {
|
|
10598
|
+
console.warn(`Skipping invalid partition '${partitionName}' in resource '${this.name}'`);
|
|
10599
|
+
continue;
|
|
10600
|
+
}
|
|
10384
10601
|
const partitionKey = this.getPartitionKey({ partitionName, id: data.id, data });
|
|
10385
10602
|
if (partitionKey) {
|
|
10386
10603
|
const mappedData = await this.schema.mapper(data);
|
|
@@ -10479,6 +10696,7 @@ ${validation.errors.join("\n")}`);
|
|
|
10479
10696
|
if (request.VersionId) data._versionId = request.VersionId;
|
|
10480
10697
|
if (request.Expiration) data._expiresAt = request.Expiration;
|
|
10481
10698
|
data._definitionHash = this.getDefinitionHash();
|
|
10699
|
+
if (data.undefined !== void 0) delete data.undefined;
|
|
10482
10700
|
this.emit("getFromPartition", data);
|
|
10483
10701
|
return data;
|
|
10484
10702
|
}
|
|
@@ -10582,7 +10800,7 @@ class Database extends EventEmitter {
|
|
|
10582
10800
|
this.version = "1";
|
|
10583
10801
|
this.s3dbVersion = (() => {
|
|
10584
10802
|
try {
|
|
10585
|
-
return true ? "
|
|
10803
|
+
return true ? "5.0.0" : "latest";
|
|
10586
10804
|
} catch (e) {
|
|
10587
10805
|
return "latest";
|
|
10588
10806
|
}
|
|
@@ -10597,14 +10815,24 @@ class Database extends EventEmitter {
|
|
|
10597
10815
|
this.passphrase = options.passphrase || "secret";
|
|
10598
10816
|
let connectionString = options.connectionString;
|
|
10599
10817
|
if (!connectionString && (options.bucket || options.accessKeyId || options.secretAccessKey)) {
|
|
10600
|
-
|
|
10601
|
-
|
|
10602
|
-
|
|
10603
|
-
accessKeyId
|
|
10604
|
-
secretAccessKey
|
|
10605
|
-
|
|
10606
|
-
forcePathStyle
|
|
10607
|
-
|
|
10818
|
+
const { bucket, region, accessKeyId, secretAccessKey, endpoint, forcePathStyle } = options;
|
|
10819
|
+
if (endpoint) {
|
|
10820
|
+
const url = new URL(endpoint);
|
|
10821
|
+
if (accessKeyId) url.username = encodeURIComponent(accessKeyId);
|
|
10822
|
+
if (secretAccessKey) url.password = encodeURIComponent(secretAccessKey);
|
|
10823
|
+
url.pathname = `/${bucket || "s3db"}`;
|
|
10824
|
+
if (forcePathStyle) {
|
|
10825
|
+
url.searchParams.set("forcePathStyle", "true");
|
|
10826
|
+
}
|
|
10827
|
+
connectionString = url.toString();
|
|
10828
|
+
} else if (accessKeyId && secretAccessKey) {
|
|
10829
|
+
const params = new URLSearchParams();
|
|
10830
|
+
params.set("region", region || "us-east-1");
|
|
10831
|
+
if (forcePathStyle) {
|
|
10832
|
+
params.set("forcePathStyle", "true");
|
|
10833
|
+
}
|
|
10834
|
+
connectionString = `s3://${encodeURIComponent(accessKeyId)}:${encodeURIComponent(secretAccessKey)}@${bucket || "s3db"}?${params.toString()}`;
|
|
10835
|
+
}
|
|
10608
10836
|
}
|
|
10609
10837
|
this.client = options.client || new Client({
|
|
10610
10838
|
verbose: this.verbose,
|
|
@@ -10640,12 +10868,12 @@ class Database extends EventEmitter {
|
|
|
10640
10868
|
passphrase: this.passphrase,
|
|
10641
10869
|
observers: [this],
|
|
10642
10870
|
cache: this.cache,
|
|
10643
|
-
timestamps: versionData.
|
|
10644
|
-
partitions: resourceMetadata.partitions || versionData.
|
|
10645
|
-
paranoid: versionData.
|
|
10646
|
-
allNestedObjectsOptional: versionData.
|
|
10647
|
-
autoDecrypt: versionData.
|
|
10648
|
-
hooks: {}
|
|
10871
|
+
timestamps: versionData.timestamps !== void 0 ? versionData.timestamps : false,
|
|
10872
|
+
partitions: resourceMetadata.partitions || versionData.partitions || {},
|
|
10873
|
+
paranoid: versionData.paranoid !== void 0 ? versionData.paranoid : true,
|
|
10874
|
+
allNestedObjectsOptional: versionData.allNestedObjectsOptional !== void 0 ? versionData.allNestedObjectsOptional : true,
|
|
10875
|
+
autoDecrypt: versionData.autoDecrypt !== void 0 ? versionData.autoDecrypt : true,
|
|
10876
|
+
hooks: versionData.hooks || {}
|
|
10649
10877
|
});
|
|
10650
10878
|
}
|
|
10651
10879
|
}
|
|
@@ -10783,15 +11011,14 @@ class Database extends EventEmitter {
|
|
|
10783
11011
|
[version]: {
|
|
10784
11012
|
hash: definitionHash,
|
|
10785
11013
|
attributes: resourceDef.attributes,
|
|
10786
|
-
options: {
|
|
10787
|
-
timestamps: resource.config.timestamps,
|
|
10788
|
-
partitions: resource.config.partitions,
|
|
10789
|
-
paranoid: resource.config.paranoid,
|
|
10790
|
-
allNestedObjectsOptional: resource.config.allNestedObjectsOptional,
|
|
10791
|
-
autoDecrypt: resource.config.autoDecrypt,
|
|
10792
|
-
cache: resource.config.cache
|
|
10793
|
-
},
|
|
10794
11014
|
behavior: resourceDef.behavior || "user-management",
|
|
11015
|
+
timestamps: resource.config.timestamps,
|
|
11016
|
+
partitions: resource.config.partitions,
|
|
11017
|
+
paranoid: resource.config.paranoid,
|
|
11018
|
+
allNestedObjectsOptional: resource.config.allNestedObjectsOptional,
|
|
11019
|
+
autoDecrypt: resource.config.autoDecrypt,
|
|
11020
|
+
cache: resource.config.cache,
|
|
11021
|
+
hooks: resource.config.hooks,
|
|
10795
11022
|
createdAt: isNewVersion ? (/* @__PURE__ */ new Date()).toISOString() : existingVersionData?.createdAt
|
|
10796
11023
|
}
|
|
10797
11024
|
}
|
|
@@ -10826,73 +11053,58 @@ class Database extends EventEmitter {
|
|
|
10826
11053
|
}
|
|
10827
11054
|
/**
|
|
10828
11055
|
* Check if a resource exists with the same definition hash
|
|
10829
|
-
* @param {
|
|
10830
|
-
* @param {
|
|
10831
|
-
* @param {Object}
|
|
10832
|
-
* @param {string} behavior - Resource behavior
|
|
10833
|
-
* @
|
|
11056
|
+
* @param {Object} config - Resource configuration
|
|
11057
|
+
* @param {string} config.name - Resource name
|
|
11058
|
+
* @param {Object} config.attributes - Resource attributes
|
|
11059
|
+
* @param {string} [config.behavior] - Resource behavior
|
|
11060
|
+
* @param {Object} [config.options] - Resource options (deprecated, use root level parameters)
|
|
11061
|
+
* @returns {Object} Result with exists and hash information
|
|
10834
11062
|
*/
|
|
10835
|
-
resourceExistsWithSameHash({ name, attributes,
|
|
11063
|
+
resourceExistsWithSameHash({ name, attributes, behavior = "user-management", options = {} }) {
|
|
10836
11064
|
if (!this.resources[name]) {
|
|
10837
11065
|
return { exists: false, sameHash: false, hash: null };
|
|
10838
11066
|
}
|
|
10839
|
-
const
|
|
11067
|
+
const existingResource = this.resources[name];
|
|
11068
|
+
const existingHash = this.generateDefinitionHash(existingResource.export());
|
|
11069
|
+
const mockResource = new Resource({
|
|
10840
11070
|
name,
|
|
10841
11071
|
attributes,
|
|
10842
11072
|
behavior,
|
|
10843
|
-
observers: [],
|
|
10844
11073
|
client: this.client,
|
|
10845
|
-
version:
|
|
11074
|
+
version: existingResource.version,
|
|
10846
11075
|
passphrase: this.passphrase,
|
|
10847
|
-
cache: this.cache,
|
|
10848
11076
|
...options
|
|
10849
11077
|
});
|
|
10850
|
-
const newHash = this.generateDefinitionHash(
|
|
10851
|
-
const existingHash = this.generateDefinitionHash(this.resources[name].export(), this.resources[name].behavior);
|
|
11078
|
+
const newHash = this.generateDefinitionHash(mockResource.export());
|
|
10852
11079
|
return {
|
|
10853
11080
|
exists: true,
|
|
10854
|
-
sameHash:
|
|
11081
|
+
sameHash: existingHash === newHash,
|
|
10855
11082
|
hash: newHash,
|
|
10856
11083
|
existingHash
|
|
10857
11084
|
};
|
|
10858
11085
|
}
|
|
10859
|
-
|
|
10860
|
-
* Create a resource only if it doesn't exist with the same definition hash
|
|
10861
|
-
* @param {Object} params - Resource parameters
|
|
10862
|
-
* @param {string} params.name - Resource name
|
|
10863
|
-
* @param {Object} params.attributes - Resource attributes
|
|
10864
|
-
* @param {Object} params.options - Resource options
|
|
10865
|
-
* @param {string} params.behavior - Resource behavior
|
|
10866
|
-
* @returns {Object} Object with resource and created flag
|
|
10867
|
-
*/
|
|
10868
|
-
async createResourceIfNotExists({ name, attributes, options = {}, behavior = "user-management" }) {
|
|
10869
|
-
const alreadyExists = !!this.resources[name];
|
|
10870
|
-
const hashCheck = this.resourceExistsWithSameHash({ name, attributes, options, behavior });
|
|
10871
|
-
if (hashCheck.exists && hashCheck.sameHash) {
|
|
10872
|
-
return {
|
|
10873
|
-
resource: this.resources[name],
|
|
10874
|
-
created: false,
|
|
10875
|
-
reason: "Resource already exists with same definition hash"
|
|
10876
|
-
};
|
|
10877
|
-
}
|
|
10878
|
-
const resource = await this.createResource({ name, attributes, options, behavior });
|
|
10879
|
-
return {
|
|
10880
|
-
resource,
|
|
10881
|
-
created: !alreadyExists,
|
|
10882
|
-
reason: alreadyExists ? "Resource updated with new definition" : "New resource created"
|
|
10883
|
-
};
|
|
10884
|
-
}
|
|
10885
|
-
async createResource({ name, attributes, options = {}, behavior = "user-management" }) {
|
|
11086
|
+
async createResource({ name, attributes, behavior = "user-management", hooks, ...config }) {
|
|
10886
11087
|
if (this.resources[name]) {
|
|
10887
11088
|
const existingResource = this.resources[name];
|
|
10888
11089
|
Object.assign(existingResource.config, {
|
|
10889
11090
|
cache: this.cache,
|
|
10890
|
-
...
|
|
11091
|
+
...config
|
|
10891
11092
|
});
|
|
10892
11093
|
if (behavior) {
|
|
10893
11094
|
existingResource.behavior = behavior;
|
|
10894
11095
|
}
|
|
10895
11096
|
existingResource.updateAttributes(attributes);
|
|
11097
|
+
if (hooks) {
|
|
11098
|
+
for (const [event, hooksArr] of Object.entries(hooks)) {
|
|
11099
|
+
if (Array.isArray(hooksArr) && existingResource.hooks[event]) {
|
|
11100
|
+
for (const fn of hooksArr) {
|
|
11101
|
+
if (typeof fn === "function") {
|
|
11102
|
+
existingResource.hooks[event].push(fn.bind(existingResource));
|
|
11103
|
+
}
|
|
11104
|
+
}
|
|
11105
|
+
}
|
|
11106
|
+
}
|
|
11107
|
+
}
|
|
10896
11108
|
const newHash = this.generateDefinitionHash(existingResource.export(), existingResource.behavior);
|
|
10897
11109
|
const existingMetadata2 = this.savedMetadata?.resources?.[name];
|
|
10898
11110
|
const currentVersion = existingMetadata2?.currentVersion || "v0";
|
|
@@ -10914,7 +11126,8 @@ class Database extends EventEmitter {
|
|
|
10914
11126
|
version,
|
|
10915
11127
|
passphrase: this.passphrase,
|
|
10916
11128
|
cache: this.cache,
|
|
10917
|
-
|
|
11129
|
+
hooks,
|
|
11130
|
+
...config
|
|
10918
11131
|
});
|
|
10919
11132
|
this.resources[name] = resource;
|
|
10920
11133
|
await this.uploadMetadataFile();
|
|
@@ -17616,6 +17829,7 @@ class CachePlugin extends Plugin {
|
|
|
17616
17829
|
}
|
|
17617
17830
|
}
|
|
17618
17831
|
|
|
17832
|
+
exports.AVAILABLE_BEHAVIORS = AVAILABLE_BEHAVIORS;
|
|
17619
17833
|
exports.AuthenticationError = AuthenticationError;
|
|
17620
17834
|
exports.BaseError = BaseError;
|
|
17621
17835
|
exports.Cache = Cache;
|
|
@@ -17623,6 +17837,7 @@ exports.CachePlugin = CachePlugin;
|
|
|
17623
17837
|
exports.Client = Client;
|
|
17624
17838
|
exports.ConnectionString = ConnectionString;
|
|
17625
17839
|
exports.CostsPlugin = CostsPlugin;
|
|
17840
|
+
exports.DEFAULT_BEHAVIOR = DEFAULT_BEHAVIOR;
|
|
17626
17841
|
exports.Database = Database;
|
|
17627
17842
|
exports.DatabaseError = DatabaseError;
|
|
17628
17843
|
exports.EncryptionError = EncryptionError;
|
|
@@ -17636,25 +17851,35 @@ exports.NotFound = NotFound;
|
|
|
17636
17851
|
exports.PermissionError = PermissionError;
|
|
17637
17852
|
exports.Plugin = Plugin;
|
|
17638
17853
|
exports.PluginObject = PluginObject;
|
|
17854
|
+
exports.Resource = Resource;
|
|
17639
17855
|
exports.ResourceIdsPageReader = ResourceIdsPageReader;
|
|
17640
17856
|
exports.ResourceIdsReader = ResourceIdsReader;
|
|
17641
17857
|
exports.ResourceNotFound = ResourceNotFound;
|
|
17642
|
-
exports.ResourceNotFoundError = ResourceNotFound;
|
|
17643
17858
|
exports.ResourceReader = ResourceReader;
|
|
17644
17859
|
exports.ResourceWriter = ResourceWriter;
|
|
17645
17860
|
exports.S3Cache = S3Cache;
|
|
17646
|
-
exports.S3DB = S3db;
|
|
17647
17861
|
exports.S3DBError = S3DBError;
|
|
17648
17862
|
exports.S3_DEFAULT_ENDPOINT = S3_DEFAULT_ENDPOINT;
|
|
17649
17863
|
exports.S3_DEFAULT_REGION = S3_DEFAULT_REGION;
|
|
17650
17864
|
exports.S3db = S3db;
|
|
17651
|
-
exports.
|
|
17865
|
+
exports.Schema = Schema;
|
|
17866
|
+
exports.SchemaActions = SchemaActions;
|
|
17652
17867
|
exports.UnknownError = UnknownError;
|
|
17653
17868
|
exports.ValidationError = ValidationError;
|
|
17654
17869
|
exports.Validator = Validator;
|
|
17655
17870
|
exports.ValidatorManager = ValidatorManager;
|
|
17871
|
+
exports.behaviors = behaviors;
|
|
17872
|
+
exports.calculateAttributeNamesSize = calculateAttributeNamesSize;
|
|
17873
|
+
exports.calculateAttributeSizes = calculateAttributeSizes;
|
|
17874
|
+
exports.calculateTotalSize = calculateTotalSize;
|
|
17875
|
+
exports.calculateUTF8Bytes = calculateUTF8Bytes;
|
|
17656
17876
|
exports.decrypt = decrypt;
|
|
17657
17877
|
exports.default = S3db;
|
|
17658
17878
|
exports.encrypt = encrypt;
|
|
17879
|
+
exports.getBehavior = getBehavior;
|
|
17880
|
+
exports.getSizeBreakdown = getSizeBreakdown;
|
|
17881
|
+
exports.idGenerator = idGenerator;
|
|
17882
|
+
exports.passwordGenerator = passwordGenerator;
|
|
17659
17883
|
exports.sha256 = sha256;
|
|
17660
17884
|
exports.streamToString = streamToString;
|
|
17885
|
+
exports.transformValue = transformValue;
|