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.iife.js
CHANGED
|
@@ -237,7 +237,7 @@ var S3DB = (function (exports, nanoid, lodashEs, promisePool, clientS3, crypto,
|
|
|
237
237
|
|
|
238
238
|
const idGenerator = nanoid.customAlphabet(nanoid.urlAlphabet, 22);
|
|
239
239
|
const passwordAlphabet = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789";
|
|
240
|
-
nanoid.customAlphabet(passwordAlphabet, 12);
|
|
240
|
+
const passwordGenerator = nanoid.customAlphabet(passwordAlphabet, 12);
|
|
241
241
|
|
|
242
242
|
var domain;
|
|
243
243
|
|
|
@@ -3382,7 +3382,7 @@ ${JSON.stringify(validation, null, 2)}`, {
|
|
|
3382
3382
|
var jsonStableStringifyExports = requireJsonStableStringify();
|
|
3383
3383
|
var jsonStableStringify = /*@__PURE__*/getDefaultExportFromCjs(jsonStableStringifyExports);
|
|
3384
3384
|
|
|
3385
|
-
async function
|
|
3385
|
+
async function secretHandler(actual, errors, schema) {
|
|
3386
3386
|
if (!this.passphrase) {
|
|
3387
3387
|
errors.push({ actual, type: "encryptionKeyMissing" });
|
|
3388
3388
|
return actual;
|
|
@@ -3395,6 +3395,10 @@ ${JSON.stringify(validation, null, 2)}`, {
|
|
|
3395
3395
|
}
|
|
3396
3396
|
return actual;
|
|
3397
3397
|
}
|
|
3398
|
+
async function jsonHandler(actual, errors, schema) {
|
|
3399
|
+
if (lodashEs.isString(actual)) return actual;
|
|
3400
|
+
return JSON.stringify(actual);
|
|
3401
|
+
}
|
|
3398
3402
|
class Validator extends FastestValidator {
|
|
3399
3403
|
constructor({ options, passphrase, autoEncrypt = true } = {}) {
|
|
3400
3404
|
super(lodashEs.merge({}, {
|
|
@@ -3416,7 +3420,7 @@ ${JSON.stringify(validation, null, 2)}`, {
|
|
|
3416
3420
|
this.autoEncrypt = autoEncrypt;
|
|
3417
3421
|
this.alias("secret", {
|
|
3418
3422
|
type: "string",
|
|
3419
|
-
custom: this.autoEncrypt ?
|
|
3423
|
+
custom: this.autoEncrypt ? secretHandler : void 0,
|
|
3420
3424
|
messages: {
|
|
3421
3425
|
string: "The '{field}' field must be a string.",
|
|
3422
3426
|
stringMin: "This secret '{field}' field length must be at least {expected} long."
|
|
@@ -3424,11 +3428,15 @@ ${JSON.stringify(validation, null, 2)}`, {
|
|
|
3424
3428
|
});
|
|
3425
3429
|
this.alias("secretAny", {
|
|
3426
3430
|
type: "any",
|
|
3427
|
-
custom: this.autoEncrypt ?
|
|
3431
|
+
custom: this.autoEncrypt ? secretHandler : void 0
|
|
3428
3432
|
});
|
|
3429
3433
|
this.alias("secretNumber", {
|
|
3430
3434
|
type: "number",
|
|
3431
|
-
custom: this.autoEncrypt ?
|
|
3435
|
+
custom: this.autoEncrypt ? secretHandler : void 0
|
|
3436
|
+
});
|
|
3437
|
+
this.alias("json", {
|
|
3438
|
+
type: "any",
|
|
3439
|
+
custom: this.autoEncrypt ? jsonHandler : void 0
|
|
3432
3440
|
});
|
|
3433
3441
|
}
|
|
3434
3442
|
}
|
|
@@ -3440,10 +3448,31 @@ ${JSON.stringify(validation, null, 2)}`, {
|
|
|
3440
3448
|
}
|
|
3441
3449
|
});
|
|
3442
3450
|
|
|
3451
|
+
function toBase36(num) {
|
|
3452
|
+
return num.toString(36);
|
|
3453
|
+
}
|
|
3454
|
+
function generateBase36Mapping(keys) {
|
|
3455
|
+
const mapping = {};
|
|
3456
|
+
const reversedMapping = {};
|
|
3457
|
+
keys.forEach((key, index) => {
|
|
3458
|
+
const base36Key = toBase36(index);
|
|
3459
|
+
mapping[key] = base36Key;
|
|
3460
|
+
reversedMapping[base36Key] = key;
|
|
3461
|
+
});
|
|
3462
|
+
return { mapping, reversedMapping };
|
|
3463
|
+
}
|
|
3443
3464
|
const SchemaActions = {
|
|
3444
3465
|
trim: (value) => value.trim(),
|
|
3445
3466
|
encrypt: (value, { passphrase }) => encrypt(value, passphrase),
|
|
3446
|
-
decrypt: (value, { passphrase }) =>
|
|
3467
|
+
decrypt: async (value, { passphrase }) => {
|
|
3468
|
+
try {
|
|
3469
|
+
const raw = await decrypt(value, passphrase);
|
|
3470
|
+
return raw;
|
|
3471
|
+
} catch (error) {
|
|
3472
|
+
console.warn(`Schema decrypt error: ${error}`, error);
|
|
3473
|
+
return value;
|
|
3474
|
+
}
|
|
3475
|
+
},
|
|
3447
3476
|
toString: (value) => String(value),
|
|
3448
3477
|
fromArray: (value, { separator }) => {
|
|
3449
3478
|
if (value === null || value === void 0 || !Array.isArray(value)) {
|
|
@@ -3534,8 +3563,9 @@ ${JSON.stringify(validation, null, 2)}`, {
|
|
|
3534
3563
|
const leafKeys = Object.keys(flatAttrs).filter((k) => !k.includes("$$"));
|
|
3535
3564
|
const objectKeys = this.extractObjectKeys(this.attributes);
|
|
3536
3565
|
const allKeys = [.../* @__PURE__ */ new Set([...leafKeys, ...objectKeys])];
|
|
3537
|
-
|
|
3538
|
-
this.map =
|
|
3566
|
+
const { mapping, reversedMapping } = generateBase36Mapping(allKeys);
|
|
3567
|
+
this.map = mapping;
|
|
3568
|
+
this.reversedMap = reversedMapping;
|
|
3539
3569
|
}
|
|
3540
3570
|
}
|
|
3541
3571
|
defaultOptions() {
|
|
@@ -3576,24 +3606,27 @@ ${JSON.stringify(validation, null, 2)}`, {
|
|
|
3576
3606
|
if (definition.includes("array")) {
|
|
3577
3607
|
this.addHook("beforeMap", name, "fromArray");
|
|
3578
3608
|
this.addHook("afterUnmap", name, "toArray");
|
|
3579
|
-
}
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
}
|
|
3584
|
-
if (this.options.autoDecrypt) {
|
|
3585
|
-
this.addHook("afterUnmap", name, "decrypt");
|
|
3586
|
-
}
|
|
3587
|
-
}
|
|
3588
|
-
if (definition.includes("number")) {
|
|
3589
|
-
this.addHook("beforeMap", name, "toString");
|
|
3590
|
-
this.addHook("afterUnmap", name, "toNumber");
|
|
3609
|
+
}
|
|
3610
|
+
if (definition.includes("secret")) {
|
|
3611
|
+
if (this.options.autoEncrypt) {
|
|
3612
|
+
this.addHook("beforeMap", name, "encrypt");
|
|
3591
3613
|
}
|
|
3592
|
-
if (
|
|
3593
|
-
this.addHook("
|
|
3594
|
-
this.addHook("afterUnmap", name, "toBool");
|
|
3614
|
+
if (this.options.autoDecrypt) {
|
|
3615
|
+
this.addHook("afterUnmap", name, "decrypt");
|
|
3595
3616
|
}
|
|
3596
3617
|
}
|
|
3618
|
+
if (definition.includes("number")) {
|
|
3619
|
+
this.addHook("beforeMap", name, "toString");
|
|
3620
|
+
this.addHook("afterUnmap", name, "toNumber");
|
|
3621
|
+
}
|
|
3622
|
+
if (definition.includes("boolean")) {
|
|
3623
|
+
this.addHook("beforeMap", name, "fromBool");
|
|
3624
|
+
this.addHook("afterUnmap", name, "toBool");
|
|
3625
|
+
}
|
|
3626
|
+
if (definition.includes("json")) {
|
|
3627
|
+
this.addHook("beforeMap", name, "toJSON");
|
|
3628
|
+
this.addHook("afterUnmap", name, "fromJSON");
|
|
3629
|
+
}
|
|
3597
3630
|
}
|
|
3598
3631
|
}
|
|
3599
3632
|
static import(data) {
|
|
@@ -8608,7 +8641,6 @@ ${JSON.stringify(validation, null, 2)}`, {
|
|
|
8608
8641
|
function calculateAttributeNamesSize(mappedObject) {
|
|
8609
8642
|
let totalSize = 0;
|
|
8610
8643
|
for (const key of Object.keys(mappedObject)) {
|
|
8611
|
-
if (key === "_v") continue;
|
|
8612
8644
|
totalSize += calculateUTF8Bytes(key);
|
|
8613
8645
|
}
|
|
8614
8646
|
return totalSize;
|
|
@@ -8652,6 +8684,30 @@ ${JSON.stringify(validation, null, 2)}`, {
|
|
|
8652
8684
|
const namesSize = calculateAttributeNamesSize(mappedObject);
|
|
8653
8685
|
return valueTotal + namesSize;
|
|
8654
8686
|
}
|
|
8687
|
+
function getSizeBreakdown(mappedObject) {
|
|
8688
|
+
const valueSizes = calculateAttributeSizes(mappedObject);
|
|
8689
|
+
const namesSize = calculateAttributeNamesSize(mappedObject);
|
|
8690
|
+
const valueTotal = Object.values(valueSizes).reduce((sum, size) => sum + size, 0);
|
|
8691
|
+
const total = valueTotal + namesSize;
|
|
8692
|
+
const sortedAttributes = Object.entries(valueSizes).sort(([, a], [, b]) => b - a).map(([key, size]) => ({
|
|
8693
|
+
attribute: key,
|
|
8694
|
+
size,
|
|
8695
|
+
percentage: (size / total * 100).toFixed(2) + "%"
|
|
8696
|
+
}));
|
|
8697
|
+
return {
|
|
8698
|
+
total,
|
|
8699
|
+
valueSizes,
|
|
8700
|
+
namesSize,
|
|
8701
|
+
valueTotal,
|
|
8702
|
+
breakdown: sortedAttributes,
|
|
8703
|
+
// Add detailed breakdown including names
|
|
8704
|
+
detailedBreakdown: {
|
|
8705
|
+
values: valueTotal,
|
|
8706
|
+
names: namesSize,
|
|
8707
|
+
total
|
|
8708
|
+
}
|
|
8709
|
+
};
|
|
8710
|
+
}
|
|
8655
8711
|
|
|
8656
8712
|
const S3_METADATA_LIMIT_BYTES = 2048;
|
|
8657
8713
|
async function handleInsert$3({ resource, data, mappedData }) {
|
|
@@ -8872,6 +8928,7 @@ ${JSON.stringify(validation, null, 2)}`, {
|
|
|
8872
8928
|
}
|
|
8873
8929
|
return behavior;
|
|
8874
8930
|
}
|
|
8931
|
+
const AVAILABLE_BEHAVIORS = Object.keys(behaviors);
|
|
8875
8932
|
const DEFAULT_BEHAVIOR = "user-management";
|
|
8876
8933
|
|
|
8877
8934
|
class Resource extends EventEmitter {
|
|
@@ -8941,17 +8998,8 @@ ${validation.errors.join("\n")}`);
|
|
|
8941
8998
|
partitions = {},
|
|
8942
8999
|
paranoid = true,
|
|
8943
9000
|
allNestedObjectsOptional = true,
|
|
8944
|
-
hooks = {}
|
|
8945
|
-
options = {}
|
|
9001
|
+
hooks = {}
|
|
8946
9002
|
} = config;
|
|
8947
|
-
const mergedOptions = {
|
|
8948
|
-
cache: typeof options.cache === "boolean" ? options.cache : cache,
|
|
8949
|
-
autoDecrypt: typeof options.autoDecrypt === "boolean" ? options.autoDecrypt : autoDecrypt,
|
|
8950
|
-
timestamps: typeof options.timestamps === "boolean" ? options.timestamps : timestamps,
|
|
8951
|
-
paranoid: typeof options.paranoid === "boolean" ? options.paranoid : paranoid,
|
|
8952
|
-
allNestedObjectsOptional: typeof options.allNestedObjectsOptional === "boolean" ? options.allNestedObjectsOptional : allNestedObjectsOptional,
|
|
8953
|
-
partitions: options.partitions || partitions || {}
|
|
8954
|
-
};
|
|
8955
9003
|
this.name = name;
|
|
8956
9004
|
this.client = client;
|
|
8957
9005
|
this.version = version;
|
|
@@ -8960,13 +9008,13 @@ ${validation.errors.join("\n")}`);
|
|
|
8960
9008
|
this.parallelism = parallelism;
|
|
8961
9009
|
this.passphrase = passphrase ?? "secret";
|
|
8962
9010
|
this.config = {
|
|
8963
|
-
cache
|
|
9011
|
+
cache,
|
|
8964
9012
|
hooks,
|
|
8965
|
-
paranoid
|
|
8966
|
-
timestamps
|
|
8967
|
-
partitions
|
|
8968
|
-
autoDecrypt
|
|
8969
|
-
allNestedObjectsOptional
|
|
9013
|
+
paranoid,
|
|
9014
|
+
timestamps,
|
|
9015
|
+
partitions,
|
|
9016
|
+
autoDecrypt,
|
|
9017
|
+
allNestedObjectsOptional
|
|
8970
9018
|
};
|
|
8971
9019
|
this.hooks = {
|
|
8972
9020
|
preInsert: [],
|
|
@@ -8977,39 +9025,7 @@ ${validation.errors.join("\n")}`);
|
|
|
8977
9025
|
afterDelete: []
|
|
8978
9026
|
};
|
|
8979
9027
|
this.attributes = attributes || {};
|
|
8980
|
-
|
|
8981
|
-
this.attributes.createdAt = "string|optional";
|
|
8982
|
-
this.attributes.updatedAt = "string|optional";
|
|
8983
|
-
if (!this.config.partitions) {
|
|
8984
|
-
this.config.partitions = {};
|
|
8985
|
-
}
|
|
8986
|
-
if (!this.config.partitions.byCreatedDate) {
|
|
8987
|
-
this.config.partitions.byCreatedDate = {
|
|
8988
|
-
fields: {
|
|
8989
|
-
createdAt: "date|maxlength:10"
|
|
8990
|
-
}
|
|
8991
|
-
};
|
|
8992
|
-
}
|
|
8993
|
-
if (!this.config.partitions.byUpdatedDate) {
|
|
8994
|
-
this.config.partitions.byUpdatedDate = {
|
|
8995
|
-
fields: {
|
|
8996
|
-
updatedAt: "date|maxlength:10"
|
|
8997
|
-
}
|
|
8998
|
-
};
|
|
8999
|
-
}
|
|
9000
|
-
}
|
|
9001
|
-
this.schema = new Schema({
|
|
9002
|
-
name,
|
|
9003
|
-
attributes: this.attributes,
|
|
9004
|
-
passphrase,
|
|
9005
|
-
version: this.version,
|
|
9006
|
-
options: {
|
|
9007
|
-
autoDecrypt: this.config.autoDecrypt,
|
|
9008
|
-
allNestedObjectsOptional: this.config.allNestedObjectsOptional
|
|
9009
|
-
}
|
|
9010
|
-
});
|
|
9011
|
-
this.setupPartitionHooks();
|
|
9012
|
-
this.validatePartitions();
|
|
9028
|
+
this.applyConfiguration();
|
|
9013
9029
|
if (hooks) {
|
|
9014
9030
|
for (const [event, hooksArr] of Object.entries(hooks)) {
|
|
9015
9031
|
if (Array.isArray(hooksArr) && this.hooks[event]) {
|
|
@@ -9048,15 +9064,17 @@ ${validation.errors.join("\n")}`);
|
|
|
9048
9064
|
return exported;
|
|
9049
9065
|
}
|
|
9050
9066
|
/**
|
|
9051
|
-
*
|
|
9052
|
-
*
|
|
9067
|
+
* Apply configuration settings (timestamps, partitions, hooks)
|
|
9068
|
+
* This method ensures that all configuration-dependent features are properly set up
|
|
9053
9069
|
*/
|
|
9054
|
-
|
|
9055
|
-
const oldAttributes = this.attributes;
|
|
9056
|
-
this.attributes = newAttributes;
|
|
9070
|
+
applyConfiguration() {
|
|
9057
9071
|
if (this.config.timestamps) {
|
|
9058
|
-
|
|
9059
|
-
|
|
9072
|
+
if (!this.attributes.createdAt) {
|
|
9073
|
+
this.attributes.createdAt = "string|optional";
|
|
9074
|
+
}
|
|
9075
|
+
if (!this.attributes.updatedAt) {
|
|
9076
|
+
this.attributes.updatedAt = "string|optional";
|
|
9077
|
+
}
|
|
9060
9078
|
if (!this.config.partitions) {
|
|
9061
9079
|
this.config.partitions = {};
|
|
9062
9080
|
}
|
|
@@ -9075,9 +9093,10 @@ ${validation.errors.join("\n")}`);
|
|
|
9075
9093
|
};
|
|
9076
9094
|
}
|
|
9077
9095
|
}
|
|
9096
|
+
this.setupPartitionHooks();
|
|
9078
9097
|
this.schema = new Schema({
|
|
9079
9098
|
name: this.name,
|
|
9080
|
-
attributes:
|
|
9099
|
+
attributes: this.attributes,
|
|
9081
9100
|
passphrase: this.passphrase,
|
|
9082
9101
|
version: this.version,
|
|
9083
9102
|
options: {
|
|
@@ -9085,8 +9104,16 @@ ${validation.errors.join("\n")}`);
|
|
|
9085
9104
|
allNestedObjectsOptional: this.config.allNestedObjectsOptional
|
|
9086
9105
|
}
|
|
9087
9106
|
});
|
|
9088
|
-
this.setupPartitionHooks();
|
|
9089
9107
|
this.validatePartitions();
|
|
9108
|
+
}
|
|
9109
|
+
/**
|
|
9110
|
+
* Update resource attributes and rebuild schema
|
|
9111
|
+
* @param {Object} newAttributes - New attributes definition
|
|
9112
|
+
*/
|
|
9113
|
+
updateAttributes(newAttributes) {
|
|
9114
|
+
const oldAttributes = this.attributes;
|
|
9115
|
+
this.attributes = newAttributes;
|
|
9116
|
+
this.applyConfiguration();
|
|
9090
9117
|
return { oldAttributes, newAttributes };
|
|
9091
9118
|
}
|
|
9092
9119
|
/**
|
|
@@ -9174,7 +9201,7 @@ ${validation.errors.join("\n")}`);
|
|
|
9174
9201
|
for (const fieldName of Object.keys(partitionDef.fields)) {
|
|
9175
9202
|
if (!this.fieldExistsInAttributes(fieldName)) {
|
|
9176
9203
|
throw new Error(
|
|
9177
|
-
`Partition '${partitionName}' uses field '${fieldName}' which does not exist in resource
|
|
9204
|
+
`Partition '${partitionName}' uses field '${fieldName}' which does not exist in resource attributes. Available fields: ${currentAttributes.join(", ")}.`
|
|
9178
9205
|
);
|
|
9179
9206
|
}
|
|
9180
9207
|
}
|
|
@@ -9287,7 +9314,11 @@ ${validation.errors.join("\n")}`);
|
|
|
9287
9314
|
if (partitionSegments.length === 0) {
|
|
9288
9315
|
return null;
|
|
9289
9316
|
}
|
|
9290
|
-
|
|
9317
|
+
const finalId = id || data?.id;
|
|
9318
|
+
if (!finalId) {
|
|
9319
|
+
return null;
|
|
9320
|
+
}
|
|
9321
|
+
return join(`resource=${this.name}`, `partition=${partitionName}`, ...partitionSegments, `id=${finalId}`);
|
|
9291
9322
|
}
|
|
9292
9323
|
/**
|
|
9293
9324
|
* Get nested field value from data object using dot notation
|
|
@@ -9516,12 +9547,12 @@ ${validation.errors.join("\n")}`);
|
|
|
9516
9547
|
* console.log(updatedUser.updatedAt); // ISO timestamp
|
|
9517
9548
|
*/
|
|
9518
9549
|
async update(id, attributes) {
|
|
9519
|
-
const
|
|
9550
|
+
const originalData = await this.get(id);
|
|
9520
9551
|
if (this.config.timestamps) {
|
|
9521
9552
|
attributes.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9522
9553
|
}
|
|
9523
9554
|
const preProcessedData = await this.executeHooks("preUpdate", attributes);
|
|
9524
|
-
const attrs = lodashEs.merge(
|
|
9555
|
+
const attrs = lodashEs.merge(originalData, preProcessedData);
|
|
9525
9556
|
delete attrs.id;
|
|
9526
9557
|
const { isValid, errors, data: validated } = await this.validate(attrs);
|
|
9527
9558
|
if (!isValid) {
|
|
@@ -9532,6 +9563,9 @@ ${validation.errors.join("\n")}`);
|
|
|
9532
9563
|
validation: errors
|
|
9533
9564
|
});
|
|
9534
9565
|
}
|
|
9566
|
+
const oldData = { ...originalData, id };
|
|
9567
|
+
const newData = { ...validated, id };
|
|
9568
|
+
await this.handlePartitionReferenceUpdates(oldData, newData);
|
|
9535
9569
|
const mappedData = await this.schema.mapper(validated);
|
|
9536
9570
|
const behaviorImpl = getBehavior(this.behavior);
|
|
9537
9571
|
const { mappedData: processedMetadata, body } = await behaviorImpl.handleUpdate({
|
|
@@ -9567,7 +9601,6 @@ ${validation.errors.join("\n")}`);
|
|
|
9567
9601
|
});
|
|
9568
9602
|
validated.id = id;
|
|
9569
9603
|
await this.executeHooks("afterUpdate", validated);
|
|
9570
|
-
await this.updatePartitionReferences(validated);
|
|
9571
9604
|
this.emit("update", preProcessedData, validated);
|
|
9572
9605
|
return validated;
|
|
9573
9606
|
}
|
|
@@ -9854,23 +9887,104 @@ ${validation.errors.join("\n")}`);
|
|
|
9854
9887
|
* });
|
|
9855
9888
|
*/
|
|
9856
9889
|
async list({ partition = null, partitionValues = {}, limit, offset = 0 } = {}) {
|
|
9857
|
-
|
|
9858
|
-
|
|
9859
|
-
|
|
9890
|
+
try {
|
|
9891
|
+
if (!partition) {
|
|
9892
|
+
let ids2 = [];
|
|
9893
|
+
try {
|
|
9894
|
+
ids2 = await this.listIds({ partition, partitionValues });
|
|
9895
|
+
} catch (listIdsError) {
|
|
9896
|
+
console.warn(`Failed to get list IDs:`, listIdsError.message);
|
|
9897
|
+
return [];
|
|
9898
|
+
}
|
|
9899
|
+
let filteredIds2 = ids2.slice(offset);
|
|
9900
|
+
if (limit) {
|
|
9901
|
+
filteredIds2 = filteredIds2.slice(0, limit);
|
|
9902
|
+
}
|
|
9903
|
+
const { results: results2, errors: errors2 } = await promisePool.PromisePool.for(filteredIds2).withConcurrency(this.parallelism).handleError(async (error, id) => {
|
|
9904
|
+
console.warn(`Failed to get resource ${id}:`, error.message);
|
|
9905
|
+
return null;
|
|
9906
|
+
}).process(async (id) => {
|
|
9907
|
+
try {
|
|
9908
|
+
return await this.get(id);
|
|
9909
|
+
} catch (error) {
|
|
9910
|
+
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9911
|
+
console.warn(`Decryption failed for ${id}, returning basic info`);
|
|
9912
|
+
return {
|
|
9913
|
+
id,
|
|
9914
|
+
_decryptionFailed: true,
|
|
9915
|
+
_error: error.message
|
|
9916
|
+
};
|
|
9917
|
+
}
|
|
9918
|
+
throw error;
|
|
9919
|
+
}
|
|
9920
|
+
});
|
|
9921
|
+
const validResults2 = results2.filter((item) => item !== null);
|
|
9922
|
+
this.emit("list", { partition, partitionValues, count: validResults2.length, errors: errors2.length });
|
|
9923
|
+
return validResults2;
|
|
9924
|
+
}
|
|
9925
|
+
if (!this.config.partitions || !this.config.partitions[partition]) {
|
|
9926
|
+
console.warn(`Partition '${partition}' not found in resource '${this.name}'`);
|
|
9927
|
+
this.emit("list", { partition, partitionValues, count: 0, errors: 0 });
|
|
9928
|
+
return [];
|
|
9929
|
+
}
|
|
9930
|
+
const partitionDef = this.config.partitions[partition];
|
|
9931
|
+
const partitionSegments = [];
|
|
9932
|
+
const sortedFields = Object.entries(partitionDef.fields).sort(([a], [b]) => a.localeCompare(b));
|
|
9933
|
+
for (const [fieldName, rule] of sortedFields) {
|
|
9934
|
+
const value = partitionValues[fieldName];
|
|
9935
|
+
if (value !== void 0 && value !== null) {
|
|
9936
|
+
const transformedValue = this.applyPartitionRule(value, rule);
|
|
9937
|
+
partitionSegments.push(`${fieldName}=${transformedValue}`);
|
|
9938
|
+
}
|
|
9939
|
+
}
|
|
9940
|
+
let prefix;
|
|
9941
|
+
if (partitionSegments.length > 0) {
|
|
9942
|
+
prefix = `resource=${this.name}/partition=${partition}/${partitionSegments.join("/")}`;
|
|
9943
|
+
} else {
|
|
9944
|
+
prefix = `resource=${this.name}/partition=${partition}`;
|
|
9945
|
+
}
|
|
9946
|
+
let keys = [];
|
|
9947
|
+
try {
|
|
9948
|
+
keys = await this.client.getAllKeys({ prefix });
|
|
9949
|
+
} catch (getKeysError) {
|
|
9950
|
+
console.warn(`Failed to get partition keys:`, getKeysError.message);
|
|
9951
|
+
return [];
|
|
9952
|
+
}
|
|
9953
|
+
const ids = keys.map((key) => {
|
|
9954
|
+
const parts = key.split("/");
|
|
9955
|
+
const idPart = parts.find((part) => part.startsWith("id="));
|
|
9956
|
+
return idPart ? idPart.replace("id=", "") : null;
|
|
9957
|
+
}).filter(Boolean);
|
|
9958
|
+
let filteredIds = ids.slice(offset);
|
|
9860
9959
|
if (limit) {
|
|
9861
|
-
|
|
9960
|
+
filteredIds = filteredIds.slice(0, limit);
|
|
9862
9961
|
}
|
|
9863
|
-
const { results
|
|
9864
|
-
console.warn(`Failed to get resource ${id}:`, error.message);
|
|
9962
|
+
const { results, errors } = await promisePool.PromisePool.for(filteredIds).withConcurrency(this.parallelism).handleError(async (error, id) => {
|
|
9963
|
+
console.warn(`Failed to get partition resource ${id}:`, error.message);
|
|
9865
9964
|
return null;
|
|
9866
9965
|
}).process(async (id) => {
|
|
9867
9966
|
try {
|
|
9868
|
-
|
|
9967
|
+
const keyForId = keys.find((key) => key.includes(`id=${id}`));
|
|
9968
|
+
if (!keyForId) {
|
|
9969
|
+
throw new Error(`Partition key not found for ID ${id}`);
|
|
9970
|
+
}
|
|
9971
|
+
const keyParts = keyForId.split("/");
|
|
9972
|
+
const actualPartitionValues = {};
|
|
9973
|
+
for (const [fieldName, rule] of sortedFields) {
|
|
9974
|
+
const fieldPart = keyParts.find((part) => part.startsWith(`${fieldName}=`));
|
|
9975
|
+
if (fieldPart) {
|
|
9976
|
+
const value = fieldPart.replace(`${fieldName}=`, "");
|
|
9977
|
+
actualPartitionValues[fieldName] = value;
|
|
9978
|
+
}
|
|
9979
|
+
}
|
|
9980
|
+
return await this.getFromPartition({ id, partitionName: partition, partitionValues: actualPartitionValues });
|
|
9869
9981
|
} catch (error) {
|
|
9870
9982
|
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9871
|
-
console.warn(`Decryption failed for ${id}, returning basic info`);
|
|
9983
|
+
console.warn(`Decryption failed for partition resource ${id}, returning basic info`);
|
|
9872
9984
|
return {
|
|
9873
9985
|
id,
|
|
9986
|
+
_partition: partition,
|
|
9987
|
+
_partitionValues: partitionValues,
|
|
9874
9988
|
_decryptionFailed: true,
|
|
9875
9989
|
_error: error.message
|
|
9876
9990
|
};
|
|
@@ -9878,62 +9992,19 @@ ${validation.errors.join("\n")}`);
|
|
|
9878
9992
|
throw error;
|
|
9879
9993
|
}
|
|
9880
9994
|
});
|
|
9881
|
-
const
|
|
9882
|
-
this.emit("list", { partition, partitionValues, count:
|
|
9883
|
-
return
|
|
9884
|
-
}
|
|
9885
|
-
|
|
9886
|
-
|
|
9887
|
-
|
|
9888
|
-
|
|
9889
|
-
const partitionSegments = [];
|
|
9890
|
-
const sortedFields = Object.entries(partitionDef.fields).sort(([a], [b]) => a.localeCompare(b));
|
|
9891
|
-
for (const [fieldName, rule] of sortedFields) {
|
|
9892
|
-
const value = partitionValues[fieldName];
|
|
9893
|
-
if (value !== void 0 && value !== null) {
|
|
9894
|
-
const transformedValue = this.applyPartitionRule(value, rule);
|
|
9895
|
-
partitionSegments.push(`${fieldName}=${transformedValue}`);
|
|
9995
|
+
const validResults = results.filter((item) => item !== null);
|
|
9996
|
+
this.emit("list", { partition, partitionValues, count: validResults.length, errors: errors.length });
|
|
9997
|
+
return validResults;
|
|
9998
|
+
} catch (error) {
|
|
9999
|
+
if (error.message.includes("Partition '") && error.message.includes("' not found")) {
|
|
10000
|
+
console.warn(`Partition error in list method:`, error.message);
|
|
10001
|
+
this.emit("list", { partition, partitionValues, count: 0, errors: 1 });
|
|
10002
|
+
return [];
|
|
9896
10003
|
}
|
|
10004
|
+
console.error(`Critical error in list method:`, error.message);
|
|
10005
|
+
this.emit("list", { partition, partitionValues, count: 0, errors: 1 });
|
|
10006
|
+
return [];
|
|
9897
10007
|
}
|
|
9898
|
-
let prefix;
|
|
9899
|
-
if (partitionSegments.length > 0) {
|
|
9900
|
-
prefix = `resource=${this.name}/partition=${partition}/${partitionSegments.join("/")}`;
|
|
9901
|
-
} else {
|
|
9902
|
-
prefix = `resource=${this.name}/partition=${partition}`;
|
|
9903
|
-
}
|
|
9904
|
-
const keys = await this.client.getAllKeys({ prefix });
|
|
9905
|
-
const ids = keys.map((key) => {
|
|
9906
|
-
const parts = key.split("/");
|
|
9907
|
-
const idPart = parts.find((part) => part.startsWith("id="));
|
|
9908
|
-
return idPart ? idPart.replace("id=", "") : null;
|
|
9909
|
-
}).filter(Boolean);
|
|
9910
|
-
let filteredIds = ids.slice(offset);
|
|
9911
|
-
if (limit) {
|
|
9912
|
-
filteredIds = filteredIds.slice(0, limit);
|
|
9913
|
-
}
|
|
9914
|
-
const { results, errors } = await promisePool.PromisePool.for(filteredIds).withConcurrency(this.parallelism).handleError(async (error, id) => {
|
|
9915
|
-
console.warn(`Failed to get partition resource ${id}:`, error.message);
|
|
9916
|
-
return null;
|
|
9917
|
-
}).process(async (id) => {
|
|
9918
|
-
try {
|
|
9919
|
-
return await this.getFromPartition({ id, partitionName: partition, partitionValues });
|
|
9920
|
-
} catch (error) {
|
|
9921
|
-
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9922
|
-
console.warn(`Decryption failed for partition resource ${id}, returning basic info`);
|
|
9923
|
-
return {
|
|
9924
|
-
id,
|
|
9925
|
-
_partition: partition,
|
|
9926
|
-
_partitionValues: partitionValues,
|
|
9927
|
-
_decryptionFailed: true,
|
|
9928
|
-
_error: error.message
|
|
9929
|
-
};
|
|
9930
|
-
}
|
|
9931
|
-
throw error;
|
|
9932
|
-
}
|
|
9933
|
-
});
|
|
9934
|
-
const validResults = results.filter((item) => item !== null);
|
|
9935
|
-
this.emit("list", { partition, partitionValues, count: validResults.length, errors: errors.length });
|
|
9936
|
-
return validResults;
|
|
9937
10008
|
}
|
|
9938
10009
|
/**
|
|
9939
10010
|
* Get multiple resources by their IDs
|
|
@@ -10040,36 +10111,67 @@ ${validation.errors.join("\n")}`);
|
|
|
10040
10111
|
* console.log(`Got ${fastPage.items.length} items`); // totalItems will be null
|
|
10041
10112
|
*/
|
|
10042
10113
|
async page({ offset = 0, size = 100, partition = null, partitionValues = {}, skipCount = false } = {}) {
|
|
10043
|
-
|
|
10044
|
-
|
|
10045
|
-
|
|
10046
|
-
|
|
10047
|
-
|
|
10048
|
-
|
|
10049
|
-
|
|
10050
|
-
|
|
10051
|
-
|
|
10052
|
-
|
|
10053
|
-
|
|
10054
|
-
|
|
10055
|
-
});
|
|
10056
|
-
const result = {
|
|
10057
|
-
items,
|
|
10058
|
-
totalItems,
|
|
10059
|
-
page,
|
|
10060
|
-
pageSize: size,
|
|
10061
|
-
totalPages,
|
|
10062
|
-
// Add additional metadata for debugging
|
|
10063
|
-
_debug: {
|
|
10064
|
-
requestedSize: size,
|
|
10065
|
-
requestedOffset: offset,
|
|
10066
|
-
actualItemsReturned: items.length,
|
|
10067
|
-
skipCount,
|
|
10068
|
-
hasTotalItems: totalItems !== null
|
|
10114
|
+
try {
|
|
10115
|
+
let totalItems = null;
|
|
10116
|
+
let totalPages = null;
|
|
10117
|
+
if (!skipCount) {
|
|
10118
|
+
try {
|
|
10119
|
+
totalItems = await this.count({ partition, partitionValues });
|
|
10120
|
+
totalPages = Math.ceil(totalItems / size);
|
|
10121
|
+
} catch (countError) {
|
|
10122
|
+
console.warn(`Failed to get count for page:`, countError.message);
|
|
10123
|
+
totalItems = null;
|
|
10124
|
+
totalPages = null;
|
|
10125
|
+
}
|
|
10069
10126
|
}
|
|
10070
|
-
|
|
10071
|
-
|
|
10072
|
-
|
|
10127
|
+
const page = Math.floor(offset / size);
|
|
10128
|
+
let items = [];
|
|
10129
|
+
try {
|
|
10130
|
+
items = await this.list({
|
|
10131
|
+
partition,
|
|
10132
|
+
partitionValues,
|
|
10133
|
+
limit: size,
|
|
10134
|
+
offset
|
|
10135
|
+
});
|
|
10136
|
+
} catch (listError) {
|
|
10137
|
+
console.warn(`Failed to get items for page:`, listError.message);
|
|
10138
|
+
items = [];
|
|
10139
|
+
}
|
|
10140
|
+
const result = {
|
|
10141
|
+
items,
|
|
10142
|
+
totalItems,
|
|
10143
|
+
page,
|
|
10144
|
+
pageSize: size,
|
|
10145
|
+
totalPages,
|
|
10146
|
+
// Add additional metadata for debugging
|
|
10147
|
+
_debug: {
|
|
10148
|
+
requestedSize: size,
|
|
10149
|
+
requestedOffset: offset,
|
|
10150
|
+
actualItemsReturned: items.length,
|
|
10151
|
+
skipCount,
|
|
10152
|
+
hasTotalItems: totalItems !== null
|
|
10153
|
+
}
|
|
10154
|
+
};
|
|
10155
|
+
this.emit("page", result);
|
|
10156
|
+
return result;
|
|
10157
|
+
} catch (error) {
|
|
10158
|
+
console.error(`Critical error in page method:`, error.message);
|
|
10159
|
+
return {
|
|
10160
|
+
items: [],
|
|
10161
|
+
totalItems: null,
|
|
10162
|
+
page: Math.floor(offset / size),
|
|
10163
|
+
pageSize: size,
|
|
10164
|
+
totalPages: null,
|
|
10165
|
+
_debug: {
|
|
10166
|
+
requestedSize: size,
|
|
10167
|
+
requestedOffset: offset,
|
|
10168
|
+
actualItemsReturned: 0,
|
|
10169
|
+
skipCount,
|
|
10170
|
+
hasTotalItems: false,
|
|
10171
|
+
error: error.message
|
|
10172
|
+
}
|
|
10173
|
+
};
|
|
10174
|
+
}
|
|
10073
10175
|
}
|
|
10074
10176
|
readable() {
|
|
10075
10177
|
const stream = new ResourceReader({ resource: this });
|
|
@@ -10362,7 +10464,118 @@ ${validation.errors.join("\n")}`);
|
|
|
10362
10464
|
return results.slice(0, limit);
|
|
10363
10465
|
}
|
|
10364
10466
|
/**
|
|
10365
|
-
*
|
|
10467
|
+
* Handle partition reference updates with change detection
|
|
10468
|
+
* @param {Object} oldData - Original object data before update
|
|
10469
|
+
* @param {Object} newData - Updated object data
|
|
10470
|
+
*/
|
|
10471
|
+
async handlePartitionReferenceUpdates(oldData, newData) {
|
|
10472
|
+
const partitions = this.config.partitions;
|
|
10473
|
+
if (!partitions || Object.keys(partitions).length === 0) {
|
|
10474
|
+
return;
|
|
10475
|
+
}
|
|
10476
|
+
for (const [partitionName, partition] of Object.entries(partitions)) {
|
|
10477
|
+
try {
|
|
10478
|
+
await this.handlePartitionReferenceUpdate(partitionName, partition, oldData, newData);
|
|
10479
|
+
} catch (error) {
|
|
10480
|
+
console.warn(`Failed to update partition references for ${partitionName}:`, error.message);
|
|
10481
|
+
}
|
|
10482
|
+
}
|
|
10483
|
+
const id = newData.id || oldData.id;
|
|
10484
|
+
for (const [partitionName, partition] of Object.entries(partitions)) {
|
|
10485
|
+
const prefix = `resource=${this.name}/partition=${partitionName}`;
|
|
10486
|
+
let allKeys = [];
|
|
10487
|
+
try {
|
|
10488
|
+
allKeys = await this.client.getAllKeys({ prefix });
|
|
10489
|
+
} catch (error) {
|
|
10490
|
+
console.warn(`Aggressive cleanup: could not list keys for partition ${partitionName}:`, error.message);
|
|
10491
|
+
continue;
|
|
10492
|
+
}
|
|
10493
|
+
const validKey = this.getPartitionKey({ partitionName, id, data: newData });
|
|
10494
|
+
for (const key of allKeys) {
|
|
10495
|
+
if (key.endsWith(`/id=${id}`) && key !== validKey) {
|
|
10496
|
+
try {
|
|
10497
|
+
await this.client.deleteObject(key);
|
|
10498
|
+
} catch (error) {
|
|
10499
|
+
console.warn(`Aggressive cleanup: could not delete stale partition key ${key}:`, error.message);
|
|
10500
|
+
}
|
|
10501
|
+
}
|
|
10502
|
+
}
|
|
10503
|
+
}
|
|
10504
|
+
}
|
|
10505
|
+
/**
|
|
10506
|
+
* Handle partition reference update for a specific partition
|
|
10507
|
+
* @param {string} partitionName - Name of the partition
|
|
10508
|
+
* @param {Object} partition - Partition definition
|
|
10509
|
+
* @param {Object} oldData - Original object data before update
|
|
10510
|
+
* @param {Object} newData - Updated object data
|
|
10511
|
+
*/
|
|
10512
|
+
async handlePartitionReferenceUpdate(partitionName, partition, oldData, newData) {
|
|
10513
|
+
const id = newData.id || oldData.id;
|
|
10514
|
+
const oldPartitionKey = this.getPartitionKey({ partitionName, id, data: oldData });
|
|
10515
|
+
const newPartitionKey = this.getPartitionKey({ partitionName, id, data: newData });
|
|
10516
|
+
if (oldPartitionKey !== newPartitionKey) {
|
|
10517
|
+
if (oldPartitionKey) {
|
|
10518
|
+
try {
|
|
10519
|
+
await this.client.deleteObject(oldPartitionKey);
|
|
10520
|
+
} catch (error) {
|
|
10521
|
+
console.warn(`Old partition object could not be deleted for ${partitionName}:`, error.message);
|
|
10522
|
+
}
|
|
10523
|
+
}
|
|
10524
|
+
if (newPartitionKey) {
|
|
10525
|
+
try {
|
|
10526
|
+
const mappedData = await this.schema.mapper(newData);
|
|
10527
|
+
if (mappedData.undefined !== void 0) delete mappedData.undefined;
|
|
10528
|
+
const behaviorImpl = getBehavior(this.behavior);
|
|
10529
|
+
const { mappedData: processedMetadata, body } = await behaviorImpl.handleUpdate({
|
|
10530
|
+
resource: this,
|
|
10531
|
+
id,
|
|
10532
|
+
data: newData,
|
|
10533
|
+
mappedData
|
|
10534
|
+
});
|
|
10535
|
+
if (processedMetadata.undefined !== void 0) delete processedMetadata.undefined;
|
|
10536
|
+
const partitionMetadata = {
|
|
10537
|
+
...processedMetadata,
|
|
10538
|
+
_version: this.version
|
|
10539
|
+
};
|
|
10540
|
+
if (partitionMetadata.undefined !== void 0) delete partitionMetadata.undefined;
|
|
10541
|
+
await this.client.putObject({
|
|
10542
|
+
key: newPartitionKey,
|
|
10543
|
+
metadata: partitionMetadata,
|
|
10544
|
+
body
|
|
10545
|
+
});
|
|
10546
|
+
} catch (error) {
|
|
10547
|
+
console.warn(`New partition object could not be created for ${partitionName}:`, error.message);
|
|
10548
|
+
}
|
|
10549
|
+
}
|
|
10550
|
+
} else if (newPartitionKey) {
|
|
10551
|
+
try {
|
|
10552
|
+
const mappedData = await this.schema.mapper(newData);
|
|
10553
|
+
if (mappedData.undefined !== void 0) delete mappedData.undefined;
|
|
10554
|
+
const behaviorImpl = getBehavior(this.behavior);
|
|
10555
|
+
const { mappedData: processedMetadata, body } = await behaviorImpl.handleUpdate({
|
|
10556
|
+
resource: this,
|
|
10557
|
+
id,
|
|
10558
|
+
data: newData,
|
|
10559
|
+
mappedData
|
|
10560
|
+
});
|
|
10561
|
+
if (processedMetadata.undefined !== void 0) delete processedMetadata.undefined;
|
|
10562
|
+
const partitionMetadata = {
|
|
10563
|
+
...processedMetadata,
|
|
10564
|
+
_version: this.version
|
|
10565
|
+
};
|
|
10566
|
+
if (partitionMetadata.undefined !== void 0) delete partitionMetadata.undefined;
|
|
10567
|
+
await this.client.putObject({
|
|
10568
|
+
key: newPartitionKey,
|
|
10569
|
+
metadata: partitionMetadata,
|
|
10570
|
+
body
|
|
10571
|
+
});
|
|
10572
|
+
} catch (error) {
|
|
10573
|
+
console.warn(`Partition object could not be updated for ${partitionName}:`, error.message);
|
|
10574
|
+
}
|
|
10575
|
+
}
|
|
10576
|
+
}
|
|
10577
|
+
/**
|
|
10578
|
+
* Update partition objects to keep them in sync (legacy method for backward compatibility)
|
|
10366
10579
|
* @param {Object} data - Updated object data
|
|
10367
10580
|
*/
|
|
10368
10581
|
async updatePartitionReferences(data) {
|
|
@@ -10371,6 +10584,10 @@ ${validation.errors.join("\n")}`);
|
|
|
10371
10584
|
return;
|
|
10372
10585
|
}
|
|
10373
10586
|
for (const [partitionName, partition] of Object.entries(partitions)) {
|
|
10587
|
+
if (!partition || !partition.fields || typeof partition.fields !== "object") {
|
|
10588
|
+
console.warn(`Skipping invalid partition '${partitionName}' in resource '${this.name}'`);
|
|
10589
|
+
continue;
|
|
10590
|
+
}
|
|
10374
10591
|
const partitionKey = this.getPartitionKey({ partitionName, id: data.id, data });
|
|
10375
10592
|
if (partitionKey) {
|
|
10376
10593
|
const mappedData = await this.schema.mapper(data);
|
|
@@ -10469,6 +10686,7 @@ ${validation.errors.join("\n")}`);
|
|
|
10469
10686
|
if (request.VersionId) data._versionId = request.VersionId;
|
|
10470
10687
|
if (request.Expiration) data._expiresAt = request.Expiration;
|
|
10471
10688
|
data._definitionHash = this.getDefinitionHash();
|
|
10689
|
+
if (data.undefined !== void 0) delete data.undefined;
|
|
10472
10690
|
this.emit("getFromPartition", data);
|
|
10473
10691
|
return data;
|
|
10474
10692
|
}
|
|
@@ -10572,7 +10790,7 @@ ${validation.errors.join("\n")}`);
|
|
|
10572
10790
|
this.version = "1";
|
|
10573
10791
|
this.s3dbVersion = (() => {
|
|
10574
10792
|
try {
|
|
10575
|
-
return true ? "
|
|
10793
|
+
return true ? "5.0.0" : "latest";
|
|
10576
10794
|
} catch (e) {
|
|
10577
10795
|
return "latest";
|
|
10578
10796
|
}
|
|
@@ -10587,14 +10805,24 @@ ${validation.errors.join("\n")}`);
|
|
|
10587
10805
|
this.passphrase = options.passphrase || "secret";
|
|
10588
10806
|
let connectionString = options.connectionString;
|
|
10589
10807
|
if (!connectionString && (options.bucket || options.accessKeyId || options.secretAccessKey)) {
|
|
10590
|
-
|
|
10591
|
-
|
|
10592
|
-
|
|
10593
|
-
accessKeyId
|
|
10594
|
-
secretAccessKey
|
|
10595
|
-
|
|
10596
|
-
forcePathStyle
|
|
10597
|
-
|
|
10808
|
+
const { bucket, region, accessKeyId, secretAccessKey, endpoint, forcePathStyle } = options;
|
|
10809
|
+
if (endpoint) {
|
|
10810
|
+
const url = new URL(endpoint);
|
|
10811
|
+
if (accessKeyId) url.username = encodeURIComponent(accessKeyId);
|
|
10812
|
+
if (secretAccessKey) url.password = encodeURIComponent(secretAccessKey);
|
|
10813
|
+
url.pathname = `/${bucket || "s3db"}`;
|
|
10814
|
+
if (forcePathStyle) {
|
|
10815
|
+
url.searchParams.set("forcePathStyle", "true");
|
|
10816
|
+
}
|
|
10817
|
+
connectionString = url.toString();
|
|
10818
|
+
} else if (accessKeyId && secretAccessKey) {
|
|
10819
|
+
const params = new URLSearchParams();
|
|
10820
|
+
params.set("region", region || "us-east-1");
|
|
10821
|
+
if (forcePathStyle) {
|
|
10822
|
+
params.set("forcePathStyle", "true");
|
|
10823
|
+
}
|
|
10824
|
+
connectionString = `s3://${encodeURIComponent(accessKeyId)}:${encodeURIComponent(secretAccessKey)}@${bucket || "s3db"}?${params.toString()}`;
|
|
10825
|
+
}
|
|
10598
10826
|
}
|
|
10599
10827
|
this.client = options.client || new Client({
|
|
10600
10828
|
verbose: this.verbose,
|
|
@@ -10630,12 +10858,12 @@ ${validation.errors.join("\n")}`);
|
|
|
10630
10858
|
passphrase: this.passphrase,
|
|
10631
10859
|
observers: [this],
|
|
10632
10860
|
cache: this.cache,
|
|
10633
|
-
timestamps: versionData.
|
|
10634
|
-
partitions: resourceMetadata.partitions || versionData.
|
|
10635
|
-
paranoid: versionData.
|
|
10636
|
-
allNestedObjectsOptional: versionData.
|
|
10637
|
-
autoDecrypt: versionData.
|
|
10638
|
-
hooks: {}
|
|
10861
|
+
timestamps: versionData.timestamps !== void 0 ? versionData.timestamps : false,
|
|
10862
|
+
partitions: resourceMetadata.partitions || versionData.partitions || {},
|
|
10863
|
+
paranoid: versionData.paranoid !== void 0 ? versionData.paranoid : true,
|
|
10864
|
+
allNestedObjectsOptional: versionData.allNestedObjectsOptional !== void 0 ? versionData.allNestedObjectsOptional : true,
|
|
10865
|
+
autoDecrypt: versionData.autoDecrypt !== void 0 ? versionData.autoDecrypt : true,
|
|
10866
|
+
hooks: versionData.hooks || {}
|
|
10639
10867
|
});
|
|
10640
10868
|
}
|
|
10641
10869
|
}
|
|
@@ -10773,15 +11001,14 @@ ${validation.errors.join("\n")}`);
|
|
|
10773
11001
|
[version]: {
|
|
10774
11002
|
hash: definitionHash,
|
|
10775
11003
|
attributes: resourceDef.attributes,
|
|
10776
|
-
options: {
|
|
10777
|
-
timestamps: resource.config.timestamps,
|
|
10778
|
-
partitions: resource.config.partitions,
|
|
10779
|
-
paranoid: resource.config.paranoid,
|
|
10780
|
-
allNestedObjectsOptional: resource.config.allNestedObjectsOptional,
|
|
10781
|
-
autoDecrypt: resource.config.autoDecrypt,
|
|
10782
|
-
cache: resource.config.cache
|
|
10783
|
-
},
|
|
10784
11004
|
behavior: resourceDef.behavior || "user-management",
|
|
11005
|
+
timestamps: resource.config.timestamps,
|
|
11006
|
+
partitions: resource.config.partitions,
|
|
11007
|
+
paranoid: resource.config.paranoid,
|
|
11008
|
+
allNestedObjectsOptional: resource.config.allNestedObjectsOptional,
|
|
11009
|
+
autoDecrypt: resource.config.autoDecrypt,
|
|
11010
|
+
cache: resource.config.cache,
|
|
11011
|
+
hooks: resource.config.hooks,
|
|
10785
11012
|
createdAt: isNewVersion ? (/* @__PURE__ */ new Date()).toISOString() : existingVersionData?.createdAt
|
|
10786
11013
|
}
|
|
10787
11014
|
}
|
|
@@ -10816,73 +11043,58 @@ ${validation.errors.join("\n")}`);
|
|
|
10816
11043
|
}
|
|
10817
11044
|
/**
|
|
10818
11045
|
* Check if a resource exists with the same definition hash
|
|
10819
|
-
* @param {
|
|
10820
|
-
* @param {
|
|
10821
|
-
* @param {Object}
|
|
10822
|
-
* @param {string} behavior - Resource behavior
|
|
10823
|
-
* @
|
|
11046
|
+
* @param {Object} config - Resource configuration
|
|
11047
|
+
* @param {string} config.name - Resource name
|
|
11048
|
+
* @param {Object} config.attributes - Resource attributes
|
|
11049
|
+
* @param {string} [config.behavior] - Resource behavior
|
|
11050
|
+
* @param {Object} [config.options] - Resource options (deprecated, use root level parameters)
|
|
11051
|
+
* @returns {Object} Result with exists and hash information
|
|
10824
11052
|
*/
|
|
10825
|
-
resourceExistsWithSameHash({ name, attributes,
|
|
11053
|
+
resourceExistsWithSameHash({ name, attributes, behavior = "user-management", options = {} }) {
|
|
10826
11054
|
if (!this.resources[name]) {
|
|
10827
11055
|
return { exists: false, sameHash: false, hash: null };
|
|
10828
11056
|
}
|
|
10829
|
-
const
|
|
11057
|
+
const existingResource = this.resources[name];
|
|
11058
|
+
const existingHash = this.generateDefinitionHash(existingResource.export());
|
|
11059
|
+
const mockResource = new Resource({
|
|
10830
11060
|
name,
|
|
10831
11061
|
attributes,
|
|
10832
11062
|
behavior,
|
|
10833
|
-
observers: [],
|
|
10834
11063
|
client: this.client,
|
|
10835
|
-
version:
|
|
11064
|
+
version: existingResource.version,
|
|
10836
11065
|
passphrase: this.passphrase,
|
|
10837
|
-
cache: this.cache,
|
|
10838
11066
|
...options
|
|
10839
11067
|
});
|
|
10840
|
-
const newHash = this.generateDefinitionHash(
|
|
10841
|
-
const existingHash = this.generateDefinitionHash(this.resources[name].export(), this.resources[name].behavior);
|
|
11068
|
+
const newHash = this.generateDefinitionHash(mockResource.export());
|
|
10842
11069
|
return {
|
|
10843
11070
|
exists: true,
|
|
10844
|
-
sameHash:
|
|
11071
|
+
sameHash: existingHash === newHash,
|
|
10845
11072
|
hash: newHash,
|
|
10846
11073
|
existingHash
|
|
10847
11074
|
};
|
|
10848
11075
|
}
|
|
10849
|
-
|
|
10850
|
-
* Create a resource only if it doesn't exist with the same definition hash
|
|
10851
|
-
* @param {Object} params - Resource parameters
|
|
10852
|
-
* @param {string} params.name - Resource name
|
|
10853
|
-
* @param {Object} params.attributes - Resource attributes
|
|
10854
|
-
* @param {Object} params.options - Resource options
|
|
10855
|
-
* @param {string} params.behavior - Resource behavior
|
|
10856
|
-
* @returns {Object} Object with resource and created flag
|
|
10857
|
-
*/
|
|
10858
|
-
async createResourceIfNotExists({ name, attributes, options = {}, behavior = "user-management" }) {
|
|
10859
|
-
const alreadyExists = !!this.resources[name];
|
|
10860
|
-
const hashCheck = this.resourceExistsWithSameHash({ name, attributes, options, behavior });
|
|
10861
|
-
if (hashCheck.exists && hashCheck.sameHash) {
|
|
10862
|
-
return {
|
|
10863
|
-
resource: this.resources[name],
|
|
10864
|
-
created: false,
|
|
10865
|
-
reason: "Resource already exists with same definition hash"
|
|
10866
|
-
};
|
|
10867
|
-
}
|
|
10868
|
-
const resource = await this.createResource({ name, attributes, options, behavior });
|
|
10869
|
-
return {
|
|
10870
|
-
resource,
|
|
10871
|
-
created: !alreadyExists,
|
|
10872
|
-
reason: alreadyExists ? "Resource updated with new definition" : "New resource created"
|
|
10873
|
-
};
|
|
10874
|
-
}
|
|
10875
|
-
async createResource({ name, attributes, options = {}, behavior = "user-management" }) {
|
|
11076
|
+
async createResource({ name, attributes, behavior = "user-management", hooks, ...config }) {
|
|
10876
11077
|
if (this.resources[name]) {
|
|
10877
11078
|
const existingResource = this.resources[name];
|
|
10878
11079
|
Object.assign(existingResource.config, {
|
|
10879
11080
|
cache: this.cache,
|
|
10880
|
-
...
|
|
11081
|
+
...config
|
|
10881
11082
|
});
|
|
10882
11083
|
if (behavior) {
|
|
10883
11084
|
existingResource.behavior = behavior;
|
|
10884
11085
|
}
|
|
10885
11086
|
existingResource.updateAttributes(attributes);
|
|
11087
|
+
if (hooks) {
|
|
11088
|
+
for (const [event, hooksArr] of Object.entries(hooks)) {
|
|
11089
|
+
if (Array.isArray(hooksArr) && existingResource.hooks[event]) {
|
|
11090
|
+
for (const fn of hooksArr) {
|
|
11091
|
+
if (typeof fn === "function") {
|
|
11092
|
+
existingResource.hooks[event].push(fn.bind(existingResource));
|
|
11093
|
+
}
|
|
11094
|
+
}
|
|
11095
|
+
}
|
|
11096
|
+
}
|
|
11097
|
+
}
|
|
10886
11098
|
const newHash = this.generateDefinitionHash(existingResource.export(), existingResource.behavior);
|
|
10887
11099
|
const existingMetadata2 = this.savedMetadata?.resources?.[name];
|
|
10888
11100
|
const currentVersion = existingMetadata2?.currentVersion || "v0";
|
|
@@ -10904,7 +11116,8 @@ ${validation.errors.join("\n")}`);
|
|
|
10904
11116
|
version,
|
|
10905
11117
|
passphrase: this.passphrase,
|
|
10906
11118
|
cache: this.cache,
|
|
10907
|
-
|
|
11119
|
+
hooks,
|
|
11120
|
+
...config
|
|
10908
11121
|
});
|
|
10909
11122
|
this.resources[name] = resource;
|
|
10910
11123
|
await this.uploadMetadataFile();
|
|
@@ -17606,6 +17819,7 @@ ${validation.errors.join("\n")}`);
|
|
|
17606
17819
|
}
|
|
17607
17820
|
}
|
|
17608
17821
|
|
|
17822
|
+
exports.AVAILABLE_BEHAVIORS = AVAILABLE_BEHAVIORS;
|
|
17609
17823
|
exports.AuthenticationError = AuthenticationError;
|
|
17610
17824
|
exports.BaseError = BaseError;
|
|
17611
17825
|
exports.Cache = Cache;
|
|
@@ -17613,6 +17827,7 @@ ${validation.errors.join("\n")}`);
|
|
|
17613
17827
|
exports.Client = Client;
|
|
17614
17828
|
exports.ConnectionString = ConnectionString;
|
|
17615
17829
|
exports.CostsPlugin = CostsPlugin;
|
|
17830
|
+
exports.DEFAULT_BEHAVIOR = DEFAULT_BEHAVIOR;
|
|
17616
17831
|
exports.Database = Database;
|
|
17617
17832
|
exports.DatabaseError = DatabaseError;
|
|
17618
17833
|
exports.EncryptionError = EncryptionError;
|
|
@@ -17626,28 +17841,38 @@ ${validation.errors.join("\n")}`);
|
|
|
17626
17841
|
exports.PermissionError = PermissionError;
|
|
17627
17842
|
exports.Plugin = Plugin;
|
|
17628
17843
|
exports.PluginObject = PluginObject;
|
|
17844
|
+
exports.Resource = Resource;
|
|
17629
17845
|
exports.ResourceIdsPageReader = ResourceIdsPageReader;
|
|
17630
17846
|
exports.ResourceIdsReader = ResourceIdsReader;
|
|
17631
17847
|
exports.ResourceNotFound = ResourceNotFound;
|
|
17632
|
-
exports.ResourceNotFoundError = ResourceNotFound;
|
|
17633
17848
|
exports.ResourceReader = ResourceReader;
|
|
17634
17849
|
exports.ResourceWriter = ResourceWriter;
|
|
17635
17850
|
exports.S3Cache = S3Cache;
|
|
17636
|
-
exports.S3DB = S3db;
|
|
17637
17851
|
exports.S3DBError = S3DBError;
|
|
17638
17852
|
exports.S3_DEFAULT_ENDPOINT = S3_DEFAULT_ENDPOINT;
|
|
17639
17853
|
exports.S3_DEFAULT_REGION = S3_DEFAULT_REGION;
|
|
17640
17854
|
exports.S3db = S3db;
|
|
17641
|
-
exports.
|
|
17855
|
+
exports.Schema = Schema;
|
|
17856
|
+
exports.SchemaActions = SchemaActions;
|
|
17642
17857
|
exports.UnknownError = UnknownError;
|
|
17643
17858
|
exports.ValidationError = ValidationError;
|
|
17644
17859
|
exports.Validator = Validator;
|
|
17645
17860
|
exports.ValidatorManager = ValidatorManager;
|
|
17861
|
+
exports.behaviors = behaviors;
|
|
17862
|
+
exports.calculateAttributeNamesSize = calculateAttributeNamesSize;
|
|
17863
|
+
exports.calculateAttributeSizes = calculateAttributeSizes;
|
|
17864
|
+
exports.calculateTotalSize = calculateTotalSize;
|
|
17865
|
+
exports.calculateUTF8Bytes = calculateUTF8Bytes;
|
|
17646
17866
|
exports.decrypt = decrypt;
|
|
17647
17867
|
exports.default = S3db;
|
|
17648
17868
|
exports.encrypt = encrypt;
|
|
17869
|
+
exports.getBehavior = getBehavior;
|
|
17870
|
+
exports.getSizeBreakdown = getSizeBreakdown;
|
|
17871
|
+
exports.idGenerator = idGenerator;
|
|
17872
|
+
exports.passwordGenerator = passwordGenerator;
|
|
17649
17873
|
exports.sha256 = sha256;
|
|
17650
17874
|
exports.streamToString = streamToString;
|
|
17875
|
+
exports.transformValue = transformValue;
|
|
17651
17876
|
|
|
17652
17877
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
17653
17878
|
|