s3db.js 4.1.13 → 5.0.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 +442 -229
- package/dist/s3db.cjs.min.js +11 -11
- package/dist/s3db.es.js +429 -228
- package/dist/s3db.es.min.js +13 -13
- package/dist/s3db.iife.js +442 -229
- 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
|
|
|
@@ -3450,10 +3450,31 @@ const ValidatorManager = new Proxy(Validator, {
|
|
|
3450
3450
|
}
|
|
3451
3451
|
});
|
|
3452
3452
|
|
|
3453
|
+
function toBase36(num) {
|
|
3454
|
+
return num.toString(36);
|
|
3455
|
+
}
|
|
3456
|
+
function generateBase36Mapping(keys) {
|
|
3457
|
+
const mapping = {};
|
|
3458
|
+
const reversedMapping = {};
|
|
3459
|
+
keys.forEach((key, index) => {
|
|
3460
|
+
const base36Key = toBase36(index);
|
|
3461
|
+
mapping[key] = base36Key;
|
|
3462
|
+
reversedMapping[base36Key] = key;
|
|
3463
|
+
});
|
|
3464
|
+
return { mapping, reversedMapping };
|
|
3465
|
+
}
|
|
3453
3466
|
const SchemaActions = {
|
|
3454
3467
|
trim: (value) => value.trim(),
|
|
3455
3468
|
encrypt: (value, { passphrase }) => encrypt(value, passphrase),
|
|
3456
|
-
decrypt: (value, { passphrase }) =>
|
|
3469
|
+
decrypt: async (value, { passphrase }) => {
|
|
3470
|
+
try {
|
|
3471
|
+
const raw = await decrypt(value, passphrase);
|
|
3472
|
+
return raw;
|
|
3473
|
+
} catch (error) {
|
|
3474
|
+
console.warn(`Schema decrypt error: ${error}`, error);
|
|
3475
|
+
return value;
|
|
3476
|
+
}
|
|
3477
|
+
},
|
|
3457
3478
|
toString: (value) => String(value),
|
|
3458
3479
|
fromArray: (value, { separator }) => {
|
|
3459
3480
|
if (value === null || value === void 0 || !Array.isArray(value)) {
|
|
@@ -3544,8 +3565,9 @@ class Schema {
|
|
|
3544
3565
|
const leafKeys = Object.keys(flatAttrs).filter((k) => !k.includes("$$"));
|
|
3545
3566
|
const objectKeys = this.extractObjectKeys(this.attributes);
|
|
3546
3567
|
const allKeys = [.../* @__PURE__ */ new Set([...leafKeys, ...objectKeys])];
|
|
3547
|
-
|
|
3548
|
-
this.map =
|
|
3568
|
+
const { mapping, reversedMapping } = generateBase36Mapping(allKeys);
|
|
3569
|
+
this.map = mapping;
|
|
3570
|
+
this.reversedMap = reversedMapping;
|
|
3549
3571
|
}
|
|
3550
3572
|
}
|
|
3551
3573
|
defaultOptions() {
|
|
@@ -8618,7 +8640,6 @@ function calculateUTF8Bytes(str) {
|
|
|
8618
8640
|
function calculateAttributeNamesSize(mappedObject) {
|
|
8619
8641
|
let totalSize = 0;
|
|
8620
8642
|
for (const key of Object.keys(mappedObject)) {
|
|
8621
|
-
if (key === "_v") continue;
|
|
8622
8643
|
totalSize += calculateUTF8Bytes(key);
|
|
8623
8644
|
}
|
|
8624
8645
|
return totalSize;
|
|
@@ -8662,6 +8683,30 @@ function calculateTotalSize(mappedObject) {
|
|
|
8662
8683
|
const namesSize = calculateAttributeNamesSize(mappedObject);
|
|
8663
8684
|
return valueTotal + namesSize;
|
|
8664
8685
|
}
|
|
8686
|
+
function getSizeBreakdown(mappedObject) {
|
|
8687
|
+
const valueSizes = calculateAttributeSizes(mappedObject);
|
|
8688
|
+
const namesSize = calculateAttributeNamesSize(mappedObject);
|
|
8689
|
+
const valueTotal = Object.values(valueSizes).reduce((sum, size) => sum + size, 0);
|
|
8690
|
+
const total = valueTotal + namesSize;
|
|
8691
|
+
const sortedAttributes = Object.entries(valueSizes).sort(([, a], [, b]) => b - a).map(([key, size]) => ({
|
|
8692
|
+
attribute: key,
|
|
8693
|
+
size,
|
|
8694
|
+
percentage: (size / total * 100).toFixed(2) + "%"
|
|
8695
|
+
}));
|
|
8696
|
+
return {
|
|
8697
|
+
total,
|
|
8698
|
+
valueSizes,
|
|
8699
|
+
namesSize,
|
|
8700
|
+
valueTotal,
|
|
8701
|
+
breakdown: sortedAttributes,
|
|
8702
|
+
// Add detailed breakdown including names
|
|
8703
|
+
detailedBreakdown: {
|
|
8704
|
+
values: valueTotal,
|
|
8705
|
+
names: namesSize,
|
|
8706
|
+
total
|
|
8707
|
+
}
|
|
8708
|
+
};
|
|
8709
|
+
}
|
|
8665
8710
|
|
|
8666
8711
|
const S3_METADATA_LIMIT_BYTES = 2048;
|
|
8667
8712
|
async function handleInsert$3({ resource, data, mappedData }) {
|
|
@@ -8882,6 +8927,7 @@ function getBehavior(behaviorName) {
|
|
|
8882
8927
|
}
|
|
8883
8928
|
return behavior;
|
|
8884
8929
|
}
|
|
8930
|
+
const AVAILABLE_BEHAVIORS = Object.keys(behaviors);
|
|
8885
8931
|
const DEFAULT_BEHAVIOR = "user-management";
|
|
8886
8932
|
|
|
8887
8933
|
class Resource extends EventEmitter {
|
|
@@ -8951,17 +8997,8 @@ ${validation.errors.join("\n")}`);
|
|
|
8951
8997
|
partitions = {},
|
|
8952
8998
|
paranoid = true,
|
|
8953
8999
|
allNestedObjectsOptional = true,
|
|
8954
|
-
hooks = {}
|
|
8955
|
-
options = {}
|
|
9000
|
+
hooks = {}
|
|
8956
9001
|
} = 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
9002
|
this.name = name;
|
|
8966
9003
|
this.client = client;
|
|
8967
9004
|
this.version = version;
|
|
@@ -8970,13 +9007,13 @@ ${validation.errors.join("\n")}`);
|
|
|
8970
9007
|
this.parallelism = parallelism;
|
|
8971
9008
|
this.passphrase = passphrase ?? "secret";
|
|
8972
9009
|
this.config = {
|
|
8973
|
-
cache
|
|
9010
|
+
cache,
|
|
8974
9011
|
hooks,
|
|
8975
|
-
paranoid
|
|
8976
|
-
timestamps
|
|
8977
|
-
partitions
|
|
8978
|
-
autoDecrypt
|
|
8979
|
-
allNestedObjectsOptional
|
|
9012
|
+
paranoid,
|
|
9013
|
+
timestamps,
|
|
9014
|
+
partitions,
|
|
9015
|
+
autoDecrypt,
|
|
9016
|
+
allNestedObjectsOptional
|
|
8980
9017
|
};
|
|
8981
9018
|
this.hooks = {
|
|
8982
9019
|
preInsert: [],
|
|
@@ -8987,39 +9024,7 @@ ${validation.errors.join("\n")}`);
|
|
|
8987
9024
|
afterDelete: []
|
|
8988
9025
|
};
|
|
8989
9026
|
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();
|
|
9027
|
+
this.applyConfiguration();
|
|
9023
9028
|
if (hooks) {
|
|
9024
9029
|
for (const [event, hooksArr] of Object.entries(hooks)) {
|
|
9025
9030
|
if (Array.isArray(hooksArr) && this.hooks[event]) {
|
|
@@ -9058,15 +9063,17 @@ ${validation.errors.join("\n")}`);
|
|
|
9058
9063
|
return exported;
|
|
9059
9064
|
}
|
|
9060
9065
|
/**
|
|
9061
|
-
*
|
|
9062
|
-
*
|
|
9066
|
+
* Apply configuration settings (timestamps, partitions, hooks)
|
|
9067
|
+
* This method ensures that all configuration-dependent features are properly set up
|
|
9063
9068
|
*/
|
|
9064
|
-
|
|
9065
|
-
const oldAttributes = this.attributes;
|
|
9066
|
-
this.attributes = newAttributes;
|
|
9069
|
+
applyConfiguration() {
|
|
9067
9070
|
if (this.config.timestamps) {
|
|
9068
|
-
|
|
9069
|
-
|
|
9071
|
+
if (!this.attributes.createdAt) {
|
|
9072
|
+
this.attributes.createdAt = "string|optional";
|
|
9073
|
+
}
|
|
9074
|
+
if (!this.attributes.updatedAt) {
|
|
9075
|
+
this.attributes.updatedAt = "string|optional";
|
|
9076
|
+
}
|
|
9070
9077
|
if (!this.config.partitions) {
|
|
9071
9078
|
this.config.partitions = {};
|
|
9072
9079
|
}
|
|
@@ -9085,9 +9092,10 @@ ${validation.errors.join("\n")}`);
|
|
|
9085
9092
|
};
|
|
9086
9093
|
}
|
|
9087
9094
|
}
|
|
9095
|
+
this.setupPartitionHooks();
|
|
9088
9096
|
this.schema = new Schema({
|
|
9089
9097
|
name: this.name,
|
|
9090
|
-
attributes:
|
|
9098
|
+
attributes: this.attributes,
|
|
9091
9099
|
passphrase: this.passphrase,
|
|
9092
9100
|
version: this.version,
|
|
9093
9101
|
options: {
|
|
@@ -9095,8 +9103,16 @@ ${validation.errors.join("\n")}`);
|
|
|
9095
9103
|
allNestedObjectsOptional: this.config.allNestedObjectsOptional
|
|
9096
9104
|
}
|
|
9097
9105
|
});
|
|
9098
|
-
this.setupPartitionHooks();
|
|
9099
9106
|
this.validatePartitions();
|
|
9107
|
+
}
|
|
9108
|
+
/**
|
|
9109
|
+
* Update resource attributes and rebuild schema
|
|
9110
|
+
* @param {Object} newAttributes - New attributes definition
|
|
9111
|
+
*/
|
|
9112
|
+
updateAttributes(newAttributes) {
|
|
9113
|
+
const oldAttributes = this.attributes;
|
|
9114
|
+
this.attributes = newAttributes;
|
|
9115
|
+
this.applyConfiguration();
|
|
9100
9116
|
return { oldAttributes, newAttributes };
|
|
9101
9117
|
}
|
|
9102
9118
|
/**
|
|
@@ -9184,7 +9200,7 @@ ${validation.errors.join("\n")}`);
|
|
|
9184
9200
|
for (const fieldName of Object.keys(partitionDef.fields)) {
|
|
9185
9201
|
if (!this.fieldExistsInAttributes(fieldName)) {
|
|
9186
9202
|
throw new Error(
|
|
9187
|
-
`Partition '${partitionName}' uses field '${fieldName}' which does not exist in resource
|
|
9203
|
+
`Partition '${partitionName}' uses field '${fieldName}' which does not exist in resource attributes. Available fields: ${currentAttributes.join(", ")}.`
|
|
9188
9204
|
);
|
|
9189
9205
|
}
|
|
9190
9206
|
}
|
|
@@ -9297,7 +9313,11 @@ ${validation.errors.join("\n")}`);
|
|
|
9297
9313
|
if (partitionSegments.length === 0) {
|
|
9298
9314
|
return null;
|
|
9299
9315
|
}
|
|
9300
|
-
|
|
9316
|
+
const finalId = id || data?.id;
|
|
9317
|
+
if (!finalId) {
|
|
9318
|
+
return null;
|
|
9319
|
+
}
|
|
9320
|
+
return join(`resource=${this.name}`, `partition=${partitionName}`, ...partitionSegments, `id=${finalId}`);
|
|
9301
9321
|
}
|
|
9302
9322
|
/**
|
|
9303
9323
|
* Get nested field value from data object using dot notation
|
|
@@ -9526,12 +9546,12 @@ ${validation.errors.join("\n")}`);
|
|
|
9526
9546
|
* console.log(updatedUser.updatedAt); // ISO timestamp
|
|
9527
9547
|
*/
|
|
9528
9548
|
async update(id, attributes) {
|
|
9529
|
-
const
|
|
9549
|
+
const originalData = await this.get(id);
|
|
9530
9550
|
if (this.config.timestamps) {
|
|
9531
9551
|
attributes.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9532
9552
|
}
|
|
9533
9553
|
const preProcessedData = await this.executeHooks("preUpdate", attributes);
|
|
9534
|
-
const attrs = lodashEs.merge(
|
|
9554
|
+
const attrs = lodashEs.merge(originalData, preProcessedData);
|
|
9535
9555
|
delete attrs.id;
|
|
9536
9556
|
const { isValid, errors, data: validated } = await this.validate(attrs);
|
|
9537
9557
|
if (!isValid) {
|
|
@@ -9542,6 +9562,9 @@ ${validation.errors.join("\n")}`);
|
|
|
9542
9562
|
validation: errors
|
|
9543
9563
|
});
|
|
9544
9564
|
}
|
|
9565
|
+
const oldData = { ...originalData, id };
|
|
9566
|
+
const newData = { ...validated, id };
|
|
9567
|
+
await this.handlePartitionReferenceUpdates(oldData, newData);
|
|
9545
9568
|
const mappedData = await this.schema.mapper(validated);
|
|
9546
9569
|
const behaviorImpl = getBehavior(this.behavior);
|
|
9547
9570
|
const { mappedData: processedMetadata, body } = await behaviorImpl.handleUpdate({
|
|
@@ -9577,7 +9600,6 @@ ${validation.errors.join("\n")}`);
|
|
|
9577
9600
|
});
|
|
9578
9601
|
validated.id = id;
|
|
9579
9602
|
await this.executeHooks("afterUpdate", validated);
|
|
9580
|
-
await this.updatePartitionReferences(validated);
|
|
9581
9603
|
this.emit("update", preProcessedData, validated);
|
|
9582
9604
|
return validated;
|
|
9583
9605
|
}
|
|
@@ -9864,23 +9886,104 @@ ${validation.errors.join("\n")}`);
|
|
|
9864
9886
|
* });
|
|
9865
9887
|
*/
|
|
9866
9888
|
async list({ partition = null, partitionValues = {}, limit, offset = 0 } = {}) {
|
|
9867
|
-
|
|
9868
|
-
|
|
9869
|
-
|
|
9889
|
+
try {
|
|
9890
|
+
if (!partition) {
|
|
9891
|
+
let ids2 = [];
|
|
9892
|
+
try {
|
|
9893
|
+
ids2 = await this.listIds({ partition, partitionValues });
|
|
9894
|
+
} catch (listIdsError) {
|
|
9895
|
+
console.warn(`Failed to get list IDs:`, listIdsError.message);
|
|
9896
|
+
return [];
|
|
9897
|
+
}
|
|
9898
|
+
let filteredIds2 = ids2.slice(offset);
|
|
9899
|
+
if (limit) {
|
|
9900
|
+
filteredIds2 = filteredIds2.slice(0, limit);
|
|
9901
|
+
}
|
|
9902
|
+
const { results: results2, errors: errors2 } = await promisePool.PromisePool.for(filteredIds2).withConcurrency(this.parallelism).handleError(async (error, id) => {
|
|
9903
|
+
console.warn(`Failed to get resource ${id}:`, error.message);
|
|
9904
|
+
return null;
|
|
9905
|
+
}).process(async (id) => {
|
|
9906
|
+
try {
|
|
9907
|
+
return await this.get(id);
|
|
9908
|
+
} catch (error) {
|
|
9909
|
+
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9910
|
+
console.warn(`Decryption failed for ${id}, returning basic info`);
|
|
9911
|
+
return {
|
|
9912
|
+
id,
|
|
9913
|
+
_decryptionFailed: true,
|
|
9914
|
+
_error: error.message
|
|
9915
|
+
};
|
|
9916
|
+
}
|
|
9917
|
+
throw error;
|
|
9918
|
+
}
|
|
9919
|
+
});
|
|
9920
|
+
const validResults2 = results2.filter((item) => item !== null);
|
|
9921
|
+
this.emit("list", { partition, partitionValues, count: validResults2.length, errors: errors2.length });
|
|
9922
|
+
return validResults2;
|
|
9923
|
+
}
|
|
9924
|
+
if (!this.config.partitions || !this.config.partitions[partition]) {
|
|
9925
|
+
console.warn(`Partition '${partition}' not found in resource '${this.name}'`);
|
|
9926
|
+
this.emit("list", { partition, partitionValues, count: 0, errors: 0 });
|
|
9927
|
+
return [];
|
|
9928
|
+
}
|
|
9929
|
+
const partitionDef = this.config.partitions[partition];
|
|
9930
|
+
const partitionSegments = [];
|
|
9931
|
+
const sortedFields = Object.entries(partitionDef.fields).sort(([a], [b]) => a.localeCompare(b));
|
|
9932
|
+
for (const [fieldName, rule] of sortedFields) {
|
|
9933
|
+
const value = partitionValues[fieldName];
|
|
9934
|
+
if (value !== void 0 && value !== null) {
|
|
9935
|
+
const transformedValue = this.applyPartitionRule(value, rule);
|
|
9936
|
+
partitionSegments.push(`${fieldName}=${transformedValue}`);
|
|
9937
|
+
}
|
|
9938
|
+
}
|
|
9939
|
+
let prefix;
|
|
9940
|
+
if (partitionSegments.length > 0) {
|
|
9941
|
+
prefix = `resource=${this.name}/partition=${partition}/${partitionSegments.join("/")}`;
|
|
9942
|
+
} else {
|
|
9943
|
+
prefix = `resource=${this.name}/partition=${partition}`;
|
|
9944
|
+
}
|
|
9945
|
+
let keys = [];
|
|
9946
|
+
try {
|
|
9947
|
+
keys = await this.client.getAllKeys({ prefix });
|
|
9948
|
+
} catch (getKeysError) {
|
|
9949
|
+
console.warn(`Failed to get partition keys:`, getKeysError.message);
|
|
9950
|
+
return [];
|
|
9951
|
+
}
|
|
9952
|
+
const ids = keys.map((key) => {
|
|
9953
|
+
const parts = key.split("/");
|
|
9954
|
+
const idPart = parts.find((part) => part.startsWith("id="));
|
|
9955
|
+
return idPart ? idPart.replace("id=", "") : null;
|
|
9956
|
+
}).filter(Boolean);
|
|
9957
|
+
let filteredIds = ids.slice(offset);
|
|
9870
9958
|
if (limit) {
|
|
9871
|
-
|
|
9959
|
+
filteredIds = filteredIds.slice(0, limit);
|
|
9872
9960
|
}
|
|
9873
|
-
const { results
|
|
9874
|
-
console.warn(`Failed to get resource ${id}:`, error.message);
|
|
9961
|
+
const { results, errors } = await promisePool.PromisePool.for(filteredIds).withConcurrency(this.parallelism).handleError(async (error, id) => {
|
|
9962
|
+
console.warn(`Failed to get partition resource ${id}:`, error.message);
|
|
9875
9963
|
return null;
|
|
9876
9964
|
}).process(async (id) => {
|
|
9877
9965
|
try {
|
|
9878
|
-
|
|
9966
|
+
const keyForId = keys.find((key) => key.includes(`id=${id}`));
|
|
9967
|
+
if (!keyForId) {
|
|
9968
|
+
throw new Error(`Partition key not found for ID ${id}`);
|
|
9969
|
+
}
|
|
9970
|
+
const keyParts = keyForId.split("/");
|
|
9971
|
+
const actualPartitionValues = {};
|
|
9972
|
+
for (const [fieldName, rule] of sortedFields) {
|
|
9973
|
+
const fieldPart = keyParts.find((part) => part.startsWith(`${fieldName}=`));
|
|
9974
|
+
if (fieldPart) {
|
|
9975
|
+
const value = fieldPart.replace(`${fieldName}=`, "");
|
|
9976
|
+
actualPartitionValues[fieldName] = value;
|
|
9977
|
+
}
|
|
9978
|
+
}
|
|
9979
|
+
return await this.getFromPartition({ id, partitionName: partition, partitionValues: actualPartitionValues });
|
|
9879
9980
|
} catch (error) {
|
|
9880
9981
|
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9881
|
-
console.warn(`Decryption failed for ${id}, returning basic info`);
|
|
9982
|
+
console.warn(`Decryption failed for partition resource ${id}, returning basic info`);
|
|
9882
9983
|
return {
|
|
9883
9984
|
id,
|
|
9985
|
+
_partition: partition,
|
|
9986
|
+
_partitionValues: partitionValues,
|
|
9884
9987
|
_decryptionFailed: true,
|
|
9885
9988
|
_error: error.message
|
|
9886
9989
|
};
|
|
@@ -9888,62 +9991,19 @@ ${validation.errors.join("\n")}`);
|
|
|
9888
9991
|
throw error;
|
|
9889
9992
|
}
|
|
9890
9993
|
});
|
|
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}`);
|
|
9994
|
+
const validResults = results.filter((item) => item !== null);
|
|
9995
|
+
this.emit("list", { partition, partitionValues, count: validResults.length, errors: errors.length });
|
|
9996
|
+
return validResults;
|
|
9997
|
+
} catch (error) {
|
|
9998
|
+
if (error.message.includes("Partition '") && error.message.includes("' not found")) {
|
|
9999
|
+
console.warn(`Partition error in list method:`, error.message);
|
|
10000
|
+
this.emit("list", { partition, partitionValues, count: 0, errors: 1 });
|
|
10001
|
+
return [];
|
|
9906
10002
|
}
|
|
10003
|
+
console.error(`Critical error in list method:`, error.message);
|
|
10004
|
+
this.emit("list", { partition, partitionValues, count: 0, errors: 1 });
|
|
10005
|
+
return [];
|
|
9907
10006
|
}
|
|
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
10007
|
}
|
|
9948
10008
|
/**
|
|
9949
10009
|
* Get multiple resources by their IDs
|
|
@@ -10050,36 +10110,67 @@ ${validation.errors.join("\n")}`);
|
|
|
10050
10110
|
* console.log(`Got ${fastPage.items.length} items`); // totalItems will be null
|
|
10051
10111
|
*/
|
|
10052
10112
|
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
|
|
10113
|
+
try {
|
|
10114
|
+
let totalItems = null;
|
|
10115
|
+
let totalPages = null;
|
|
10116
|
+
if (!skipCount) {
|
|
10117
|
+
try {
|
|
10118
|
+
totalItems = await this.count({ partition, partitionValues });
|
|
10119
|
+
totalPages = Math.ceil(totalItems / size);
|
|
10120
|
+
} catch (countError) {
|
|
10121
|
+
console.warn(`Failed to get count for page:`, countError.message);
|
|
10122
|
+
totalItems = null;
|
|
10123
|
+
totalPages = null;
|
|
10124
|
+
}
|
|
10079
10125
|
}
|
|
10080
|
-
|
|
10081
|
-
|
|
10082
|
-
|
|
10126
|
+
const page = Math.floor(offset / size);
|
|
10127
|
+
let items = [];
|
|
10128
|
+
try {
|
|
10129
|
+
items = await this.list({
|
|
10130
|
+
partition,
|
|
10131
|
+
partitionValues,
|
|
10132
|
+
limit: size,
|
|
10133
|
+
offset
|
|
10134
|
+
});
|
|
10135
|
+
} catch (listError) {
|
|
10136
|
+
console.warn(`Failed to get items for page:`, listError.message);
|
|
10137
|
+
items = [];
|
|
10138
|
+
}
|
|
10139
|
+
const result = {
|
|
10140
|
+
items,
|
|
10141
|
+
totalItems,
|
|
10142
|
+
page,
|
|
10143
|
+
pageSize: size,
|
|
10144
|
+
totalPages,
|
|
10145
|
+
// Add additional metadata for debugging
|
|
10146
|
+
_debug: {
|
|
10147
|
+
requestedSize: size,
|
|
10148
|
+
requestedOffset: offset,
|
|
10149
|
+
actualItemsReturned: items.length,
|
|
10150
|
+
skipCount,
|
|
10151
|
+
hasTotalItems: totalItems !== null
|
|
10152
|
+
}
|
|
10153
|
+
};
|
|
10154
|
+
this.emit("page", result);
|
|
10155
|
+
return result;
|
|
10156
|
+
} catch (error) {
|
|
10157
|
+
console.error(`Critical error in page method:`, error.message);
|
|
10158
|
+
return {
|
|
10159
|
+
items: [],
|
|
10160
|
+
totalItems: null,
|
|
10161
|
+
page: Math.floor(offset / size),
|
|
10162
|
+
pageSize: size,
|
|
10163
|
+
totalPages: null,
|
|
10164
|
+
_debug: {
|
|
10165
|
+
requestedSize: size,
|
|
10166
|
+
requestedOffset: offset,
|
|
10167
|
+
actualItemsReturned: 0,
|
|
10168
|
+
skipCount,
|
|
10169
|
+
hasTotalItems: false,
|
|
10170
|
+
error: error.message
|
|
10171
|
+
}
|
|
10172
|
+
};
|
|
10173
|
+
}
|
|
10083
10174
|
}
|
|
10084
10175
|
readable() {
|
|
10085
10176
|
const stream = new ResourceReader({ resource: this });
|
|
@@ -10372,7 +10463,118 @@ ${validation.errors.join("\n")}`);
|
|
|
10372
10463
|
return results.slice(0, limit);
|
|
10373
10464
|
}
|
|
10374
10465
|
/**
|
|
10375
|
-
*
|
|
10466
|
+
* Handle partition reference updates with change detection
|
|
10467
|
+
* @param {Object} oldData - Original object data before update
|
|
10468
|
+
* @param {Object} newData - Updated object data
|
|
10469
|
+
*/
|
|
10470
|
+
async handlePartitionReferenceUpdates(oldData, newData) {
|
|
10471
|
+
const partitions = this.config.partitions;
|
|
10472
|
+
if (!partitions || Object.keys(partitions).length === 0) {
|
|
10473
|
+
return;
|
|
10474
|
+
}
|
|
10475
|
+
for (const [partitionName, partition] of Object.entries(partitions)) {
|
|
10476
|
+
try {
|
|
10477
|
+
await this.handlePartitionReferenceUpdate(partitionName, partition, oldData, newData);
|
|
10478
|
+
} catch (error) {
|
|
10479
|
+
console.warn(`Failed to update partition references for ${partitionName}:`, error.message);
|
|
10480
|
+
}
|
|
10481
|
+
}
|
|
10482
|
+
const id = newData.id || oldData.id;
|
|
10483
|
+
for (const [partitionName, partition] of Object.entries(partitions)) {
|
|
10484
|
+
const prefix = `resource=${this.name}/partition=${partitionName}`;
|
|
10485
|
+
let allKeys = [];
|
|
10486
|
+
try {
|
|
10487
|
+
allKeys = await this.client.getAllKeys({ prefix });
|
|
10488
|
+
} catch (error) {
|
|
10489
|
+
console.warn(`Aggressive cleanup: could not list keys for partition ${partitionName}:`, error.message);
|
|
10490
|
+
continue;
|
|
10491
|
+
}
|
|
10492
|
+
const validKey = this.getPartitionKey({ partitionName, id, data: newData });
|
|
10493
|
+
for (const key of allKeys) {
|
|
10494
|
+
if (key.endsWith(`/id=${id}`) && key !== validKey) {
|
|
10495
|
+
try {
|
|
10496
|
+
await this.client.deleteObject(key);
|
|
10497
|
+
} catch (error) {
|
|
10498
|
+
console.warn(`Aggressive cleanup: could not delete stale partition key ${key}:`, error.message);
|
|
10499
|
+
}
|
|
10500
|
+
}
|
|
10501
|
+
}
|
|
10502
|
+
}
|
|
10503
|
+
}
|
|
10504
|
+
/**
|
|
10505
|
+
* Handle partition reference update for a specific partition
|
|
10506
|
+
* @param {string} partitionName - Name of the partition
|
|
10507
|
+
* @param {Object} partition - Partition definition
|
|
10508
|
+
* @param {Object} oldData - Original object data before update
|
|
10509
|
+
* @param {Object} newData - Updated object data
|
|
10510
|
+
*/
|
|
10511
|
+
async handlePartitionReferenceUpdate(partitionName, partition, oldData, newData) {
|
|
10512
|
+
const id = newData.id || oldData.id;
|
|
10513
|
+
const oldPartitionKey = this.getPartitionKey({ partitionName, id, data: oldData });
|
|
10514
|
+
const newPartitionKey = this.getPartitionKey({ partitionName, id, data: newData });
|
|
10515
|
+
if (oldPartitionKey !== newPartitionKey) {
|
|
10516
|
+
if (oldPartitionKey) {
|
|
10517
|
+
try {
|
|
10518
|
+
await this.client.deleteObject(oldPartitionKey);
|
|
10519
|
+
} catch (error) {
|
|
10520
|
+
console.warn(`Old partition object could not be deleted for ${partitionName}:`, error.message);
|
|
10521
|
+
}
|
|
10522
|
+
}
|
|
10523
|
+
if (newPartitionKey) {
|
|
10524
|
+
try {
|
|
10525
|
+
const mappedData = await this.schema.mapper(newData);
|
|
10526
|
+
if (mappedData.undefined !== void 0) delete mappedData.undefined;
|
|
10527
|
+
const behaviorImpl = getBehavior(this.behavior);
|
|
10528
|
+
const { mappedData: processedMetadata, body } = await behaviorImpl.handleUpdate({
|
|
10529
|
+
resource: this,
|
|
10530
|
+
id,
|
|
10531
|
+
data: newData,
|
|
10532
|
+
mappedData
|
|
10533
|
+
});
|
|
10534
|
+
if (processedMetadata.undefined !== void 0) delete processedMetadata.undefined;
|
|
10535
|
+
const partitionMetadata = {
|
|
10536
|
+
...processedMetadata,
|
|
10537
|
+
_version: this.version
|
|
10538
|
+
};
|
|
10539
|
+
if (partitionMetadata.undefined !== void 0) delete partitionMetadata.undefined;
|
|
10540
|
+
await this.client.putObject({
|
|
10541
|
+
key: newPartitionKey,
|
|
10542
|
+
metadata: partitionMetadata,
|
|
10543
|
+
body
|
|
10544
|
+
});
|
|
10545
|
+
} catch (error) {
|
|
10546
|
+
console.warn(`New partition object could not be created for ${partitionName}:`, error.message);
|
|
10547
|
+
}
|
|
10548
|
+
}
|
|
10549
|
+
} else if (newPartitionKey) {
|
|
10550
|
+
try {
|
|
10551
|
+
const mappedData = await this.schema.mapper(newData);
|
|
10552
|
+
if (mappedData.undefined !== void 0) delete mappedData.undefined;
|
|
10553
|
+
const behaviorImpl = getBehavior(this.behavior);
|
|
10554
|
+
const { mappedData: processedMetadata, body } = await behaviorImpl.handleUpdate({
|
|
10555
|
+
resource: this,
|
|
10556
|
+
id,
|
|
10557
|
+
data: newData,
|
|
10558
|
+
mappedData
|
|
10559
|
+
});
|
|
10560
|
+
if (processedMetadata.undefined !== void 0) delete processedMetadata.undefined;
|
|
10561
|
+
const partitionMetadata = {
|
|
10562
|
+
...processedMetadata,
|
|
10563
|
+
_version: this.version
|
|
10564
|
+
};
|
|
10565
|
+
if (partitionMetadata.undefined !== void 0) delete partitionMetadata.undefined;
|
|
10566
|
+
await this.client.putObject({
|
|
10567
|
+
key: newPartitionKey,
|
|
10568
|
+
metadata: partitionMetadata,
|
|
10569
|
+
body
|
|
10570
|
+
});
|
|
10571
|
+
} catch (error) {
|
|
10572
|
+
console.warn(`Partition object could not be updated for ${partitionName}:`, error.message);
|
|
10573
|
+
}
|
|
10574
|
+
}
|
|
10575
|
+
}
|
|
10576
|
+
/**
|
|
10577
|
+
* Update partition objects to keep them in sync (legacy method for backward compatibility)
|
|
10376
10578
|
* @param {Object} data - Updated object data
|
|
10377
10579
|
*/
|
|
10378
10580
|
async updatePartitionReferences(data) {
|
|
@@ -10381,6 +10583,10 @@ ${validation.errors.join("\n")}`);
|
|
|
10381
10583
|
return;
|
|
10382
10584
|
}
|
|
10383
10585
|
for (const [partitionName, partition] of Object.entries(partitions)) {
|
|
10586
|
+
if (!partition || !partition.fields || typeof partition.fields !== "object") {
|
|
10587
|
+
console.warn(`Skipping invalid partition '${partitionName}' in resource '${this.name}'`);
|
|
10588
|
+
continue;
|
|
10589
|
+
}
|
|
10384
10590
|
const partitionKey = this.getPartitionKey({ partitionName, id: data.id, data });
|
|
10385
10591
|
if (partitionKey) {
|
|
10386
10592
|
const mappedData = await this.schema.mapper(data);
|
|
@@ -10479,6 +10685,7 @@ ${validation.errors.join("\n")}`);
|
|
|
10479
10685
|
if (request.VersionId) data._versionId = request.VersionId;
|
|
10480
10686
|
if (request.Expiration) data._expiresAt = request.Expiration;
|
|
10481
10687
|
data._definitionHash = this.getDefinitionHash();
|
|
10688
|
+
if (data.undefined !== void 0) delete data.undefined;
|
|
10482
10689
|
this.emit("getFromPartition", data);
|
|
10483
10690
|
return data;
|
|
10484
10691
|
}
|
|
@@ -10582,7 +10789,7 @@ class Database extends EventEmitter {
|
|
|
10582
10789
|
this.version = "1";
|
|
10583
10790
|
this.s3dbVersion = (() => {
|
|
10584
10791
|
try {
|
|
10585
|
-
return true ? "
|
|
10792
|
+
return true ? "5.0.0" : "latest";
|
|
10586
10793
|
} catch (e) {
|
|
10587
10794
|
return "latest";
|
|
10588
10795
|
}
|
|
@@ -10597,14 +10804,24 @@ class Database extends EventEmitter {
|
|
|
10597
10804
|
this.passphrase = options.passphrase || "secret";
|
|
10598
10805
|
let connectionString = options.connectionString;
|
|
10599
10806
|
if (!connectionString && (options.bucket || options.accessKeyId || options.secretAccessKey)) {
|
|
10600
|
-
|
|
10601
|
-
|
|
10602
|
-
|
|
10603
|
-
accessKeyId
|
|
10604
|
-
secretAccessKey
|
|
10605
|
-
|
|
10606
|
-
forcePathStyle
|
|
10607
|
-
|
|
10807
|
+
const { bucket, region, accessKeyId, secretAccessKey, endpoint, forcePathStyle } = options;
|
|
10808
|
+
if (endpoint) {
|
|
10809
|
+
const url = new URL(endpoint);
|
|
10810
|
+
if (accessKeyId) url.username = encodeURIComponent(accessKeyId);
|
|
10811
|
+
if (secretAccessKey) url.password = encodeURIComponent(secretAccessKey);
|
|
10812
|
+
url.pathname = `/${bucket || "s3db"}`;
|
|
10813
|
+
if (forcePathStyle) {
|
|
10814
|
+
url.searchParams.set("forcePathStyle", "true");
|
|
10815
|
+
}
|
|
10816
|
+
connectionString = url.toString();
|
|
10817
|
+
} else if (accessKeyId && secretAccessKey) {
|
|
10818
|
+
const params = new URLSearchParams();
|
|
10819
|
+
params.set("region", region || "us-east-1");
|
|
10820
|
+
if (forcePathStyle) {
|
|
10821
|
+
params.set("forcePathStyle", "true");
|
|
10822
|
+
}
|
|
10823
|
+
connectionString = `s3://${encodeURIComponent(accessKeyId)}:${encodeURIComponent(secretAccessKey)}@${bucket || "s3db"}?${params.toString()}`;
|
|
10824
|
+
}
|
|
10608
10825
|
}
|
|
10609
10826
|
this.client = options.client || new Client({
|
|
10610
10827
|
verbose: this.verbose,
|
|
@@ -10640,12 +10857,12 @@ class Database extends EventEmitter {
|
|
|
10640
10857
|
passphrase: this.passphrase,
|
|
10641
10858
|
observers: [this],
|
|
10642
10859
|
cache: this.cache,
|
|
10643
|
-
timestamps: versionData.
|
|
10644
|
-
partitions: resourceMetadata.partitions || versionData.
|
|
10645
|
-
paranoid: versionData.
|
|
10646
|
-
allNestedObjectsOptional: versionData.
|
|
10647
|
-
autoDecrypt: versionData.
|
|
10648
|
-
hooks: versionData.
|
|
10860
|
+
timestamps: versionData.timestamps !== void 0 ? versionData.timestamps : false,
|
|
10861
|
+
partitions: resourceMetadata.partitions || versionData.partitions || {},
|
|
10862
|
+
paranoid: versionData.paranoid !== void 0 ? versionData.paranoid : true,
|
|
10863
|
+
allNestedObjectsOptional: versionData.allNestedObjectsOptional !== void 0 ? versionData.allNestedObjectsOptional : true,
|
|
10864
|
+
autoDecrypt: versionData.autoDecrypt !== void 0 ? versionData.autoDecrypt : true,
|
|
10865
|
+
hooks: versionData.hooks || {}
|
|
10649
10866
|
});
|
|
10650
10867
|
}
|
|
10651
10868
|
}
|
|
@@ -10783,16 +11000,14 @@ class Database extends EventEmitter {
|
|
|
10783
11000
|
[version]: {
|
|
10784
11001
|
hash: definitionHash,
|
|
10785
11002
|
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
|
-
hooks: resourceDef.hooks || {}
|
|
10794
|
-
},
|
|
10795
11003
|
behavior: resourceDef.behavior || "user-management",
|
|
11004
|
+
timestamps: resource.config.timestamps,
|
|
11005
|
+
partitions: resource.config.partitions,
|
|
11006
|
+
paranoid: resource.config.paranoid,
|
|
11007
|
+
allNestedObjectsOptional: resource.config.allNestedObjectsOptional,
|
|
11008
|
+
autoDecrypt: resource.config.autoDecrypt,
|
|
11009
|
+
cache: resource.config.cache,
|
|
11010
|
+
hooks: resource.config.hooks,
|
|
10796
11011
|
createdAt: isNewVersion ? (/* @__PURE__ */ new Date()).toISOString() : existingVersionData?.createdAt
|
|
10797
11012
|
}
|
|
10798
11013
|
}
|
|
@@ -10827,73 +11042,58 @@ class Database extends EventEmitter {
|
|
|
10827
11042
|
}
|
|
10828
11043
|
/**
|
|
10829
11044
|
* Check if a resource exists with the same definition hash
|
|
10830
|
-
* @param {
|
|
10831
|
-
* @param {
|
|
10832
|
-
* @param {Object}
|
|
10833
|
-
* @param {string} behavior - Resource behavior
|
|
10834
|
-
* @
|
|
11045
|
+
* @param {Object} config - Resource configuration
|
|
11046
|
+
* @param {string} config.name - Resource name
|
|
11047
|
+
* @param {Object} config.attributes - Resource attributes
|
|
11048
|
+
* @param {string} [config.behavior] - Resource behavior
|
|
11049
|
+
* @param {Object} [config.options] - Resource options (deprecated, use root level parameters)
|
|
11050
|
+
* @returns {Object} Result with exists and hash information
|
|
10835
11051
|
*/
|
|
10836
|
-
resourceExistsWithSameHash({ name, attributes,
|
|
11052
|
+
resourceExistsWithSameHash({ name, attributes, behavior = "user-management", options = {} }) {
|
|
10837
11053
|
if (!this.resources[name]) {
|
|
10838
11054
|
return { exists: false, sameHash: false, hash: null };
|
|
10839
11055
|
}
|
|
10840
|
-
const
|
|
11056
|
+
const existingResource = this.resources[name];
|
|
11057
|
+
const existingHash = this.generateDefinitionHash(existingResource.export());
|
|
11058
|
+
const mockResource = new Resource({
|
|
10841
11059
|
name,
|
|
10842
11060
|
attributes,
|
|
10843
11061
|
behavior,
|
|
10844
|
-
observers: [],
|
|
10845
11062
|
client: this.client,
|
|
10846
|
-
version:
|
|
11063
|
+
version: existingResource.version,
|
|
10847
11064
|
passphrase: this.passphrase,
|
|
10848
|
-
cache: this.cache,
|
|
10849
11065
|
...options
|
|
10850
11066
|
});
|
|
10851
|
-
const newHash = this.generateDefinitionHash(
|
|
10852
|
-
const existingHash = this.generateDefinitionHash(this.resources[name].export(), this.resources[name].behavior);
|
|
11067
|
+
const newHash = this.generateDefinitionHash(mockResource.export());
|
|
10853
11068
|
return {
|
|
10854
11069
|
exists: true,
|
|
10855
|
-
sameHash:
|
|
11070
|
+
sameHash: existingHash === newHash,
|
|
10856
11071
|
hash: newHash,
|
|
10857
11072
|
existingHash
|
|
10858
11073
|
};
|
|
10859
11074
|
}
|
|
10860
|
-
|
|
10861
|
-
* Create a resource only if it doesn't exist with the same definition hash
|
|
10862
|
-
* @param {Object} params - Resource parameters
|
|
10863
|
-
* @param {string} params.name - Resource name
|
|
10864
|
-
* @param {Object} params.attributes - Resource attributes
|
|
10865
|
-
* @param {Object} params.options - Resource options
|
|
10866
|
-
* @param {string} params.behavior - Resource behavior
|
|
10867
|
-
* @returns {Object} Object with resource and created flag
|
|
10868
|
-
*/
|
|
10869
|
-
async createResourceIfNotExists({ name, attributes, options = {}, behavior = "user-management" }) {
|
|
10870
|
-
const alreadyExists = !!this.resources[name];
|
|
10871
|
-
const hashCheck = this.resourceExistsWithSameHash({ name, attributes, options, behavior });
|
|
10872
|
-
if (hashCheck.exists && hashCheck.sameHash) {
|
|
10873
|
-
return {
|
|
10874
|
-
resource: this.resources[name],
|
|
10875
|
-
created: false,
|
|
10876
|
-
reason: "Resource already exists with same definition hash"
|
|
10877
|
-
};
|
|
10878
|
-
}
|
|
10879
|
-
const resource = await this.createResource({ name, attributes, options, behavior });
|
|
10880
|
-
return {
|
|
10881
|
-
resource,
|
|
10882
|
-
created: !alreadyExists,
|
|
10883
|
-
reason: alreadyExists ? "Resource updated with new definition" : "New resource created"
|
|
10884
|
-
};
|
|
10885
|
-
}
|
|
10886
|
-
async createResource({ name, attributes, options = {}, behavior = "user-management" }) {
|
|
11075
|
+
async createResource({ name, attributes, behavior = "user-management", hooks, ...config }) {
|
|
10887
11076
|
if (this.resources[name]) {
|
|
10888
11077
|
const existingResource = this.resources[name];
|
|
10889
11078
|
Object.assign(existingResource.config, {
|
|
10890
11079
|
cache: this.cache,
|
|
10891
|
-
...
|
|
11080
|
+
...config
|
|
10892
11081
|
});
|
|
10893
11082
|
if (behavior) {
|
|
10894
11083
|
existingResource.behavior = behavior;
|
|
10895
11084
|
}
|
|
10896
11085
|
existingResource.updateAttributes(attributes);
|
|
11086
|
+
if (hooks) {
|
|
11087
|
+
for (const [event, hooksArr] of Object.entries(hooks)) {
|
|
11088
|
+
if (Array.isArray(hooksArr) && existingResource.hooks[event]) {
|
|
11089
|
+
for (const fn of hooksArr) {
|
|
11090
|
+
if (typeof fn === "function") {
|
|
11091
|
+
existingResource.hooks[event].push(fn.bind(existingResource));
|
|
11092
|
+
}
|
|
11093
|
+
}
|
|
11094
|
+
}
|
|
11095
|
+
}
|
|
11096
|
+
}
|
|
10897
11097
|
const newHash = this.generateDefinitionHash(existingResource.export(), existingResource.behavior);
|
|
10898
11098
|
const existingMetadata2 = this.savedMetadata?.resources?.[name];
|
|
10899
11099
|
const currentVersion = existingMetadata2?.currentVersion || "v0";
|
|
@@ -10915,7 +11115,8 @@ class Database extends EventEmitter {
|
|
|
10915
11115
|
version,
|
|
10916
11116
|
passphrase: this.passphrase,
|
|
10917
11117
|
cache: this.cache,
|
|
10918
|
-
|
|
11118
|
+
hooks,
|
|
11119
|
+
...config
|
|
10919
11120
|
});
|
|
10920
11121
|
this.resources[name] = resource;
|
|
10921
11122
|
await this.uploadMetadataFile();
|
|
@@ -17617,6 +17818,7 @@ class CachePlugin extends Plugin {
|
|
|
17617
17818
|
}
|
|
17618
17819
|
}
|
|
17619
17820
|
|
|
17821
|
+
exports.AVAILABLE_BEHAVIORS = AVAILABLE_BEHAVIORS;
|
|
17620
17822
|
exports.AuthenticationError = AuthenticationError;
|
|
17621
17823
|
exports.BaseError = BaseError;
|
|
17622
17824
|
exports.Cache = Cache;
|
|
@@ -17624,6 +17826,7 @@ exports.CachePlugin = CachePlugin;
|
|
|
17624
17826
|
exports.Client = Client;
|
|
17625
17827
|
exports.ConnectionString = ConnectionString;
|
|
17626
17828
|
exports.CostsPlugin = CostsPlugin;
|
|
17829
|
+
exports.DEFAULT_BEHAVIOR = DEFAULT_BEHAVIOR;
|
|
17627
17830
|
exports.Database = Database;
|
|
17628
17831
|
exports.DatabaseError = DatabaseError;
|
|
17629
17832
|
exports.EncryptionError = EncryptionError;
|
|
@@ -17637,25 +17840,35 @@ exports.NotFound = NotFound;
|
|
|
17637
17840
|
exports.PermissionError = PermissionError;
|
|
17638
17841
|
exports.Plugin = Plugin;
|
|
17639
17842
|
exports.PluginObject = PluginObject;
|
|
17843
|
+
exports.Resource = Resource;
|
|
17640
17844
|
exports.ResourceIdsPageReader = ResourceIdsPageReader;
|
|
17641
17845
|
exports.ResourceIdsReader = ResourceIdsReader;
|
|
17642
17846
|
exports.ResourceNotFound = ResourceNotFound;
|
|
17643
|
-
exports.ResourceNotFoundError = ResourceNotFound;
|
|
17644
17847
|
exports.ResourceReader = ResourceReader;
|
|
17645
17848
|
exports.ResourceWriter = ResourceWriter;
|
|
17646
17849
|
exports.S3Cache = S3Cache;
|
|
17647
|
-
exports.S3DB = S3db;
|
|
17648
17850
|
exports.S3DBError = S3DBError;
|
|
17649
17851
|
exports.S3_DEFAULT_ENDPOINT = S3_DEFAULT_ENDPOINT;
|
|
17650
17852
|
exports.S3_DEFAULT_REGION = S3_DEFAULT_REGION;
|
|
17651
17853
|
exports.S3db = S3db;
|
|
17652
|
-
exports.
|
|
17854
|
+
exports.Schema = Schema;
|
|
17855
|
+
exports.SchemaActions = SchemaActions;
|
|
17653
17856
|
exports.UnknownError = UnknownError;
|
|
17654
17857
|
exports.ValidationError = ValidationError;
|
|
17655
17858
|
exports.Validator = Validator;
|
|
17656
17859
|
exports.ValidatorManager = ValidatorManager;
|
|
17860
|
+
exports.behaviors = behaviors;
|
|
17861
|
+
exports.calculateAttributeNamesSize = calculateAttributeNamesSize;
|
|
17862
|
+
exports.calculateAttributeSizes = calculateAttributeSizes;
|
|
17863
|
+
exports.calculateTotalSize = calculateTotalSize;
|
|
17864
|
+
exports.calculateUTF8Bytes = calculateUTF8Bytes;
|
|
17657
17865
|
exports.decrypt = decrypt;
|
|
17658
17866
|
exports.default = S3db;
|
|
17659
17867
|
exports.encrypt = encrypt;
|
|
17868
|
+
exports.getBehavior = getBehavior;
|
|
17869
|
+
exports.getSizeBreakdown = getSizeBreakdown;
|
|
17870
|
+
exports.idGenerator = idGenerator;
|
|
17871
|
+
exports.passwordGenerator = passwordGenerator;
|
|
17660
17872
|
exports.sha256 = sha256;
|
|
17661
17873
|
exports.streamToString = streamToString;
|
|
17874
|
+
exports.transformValue = transformValue;
|