s3db.js 4.1.14 → 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 -228
- package/dist/s3db.cjs.min.js +11 -11
- package/dist/s3db.es.js +429 -227
- package/dist/s3db.es.min.js +13 -13
- package/dist/s3db.iife.js +442 -228
- package/dist/s3db.iife.min.js +11 -11
- package/package.json +14 -14
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
|
|
|
@@ -3440,10 +3440,31 @@ ${JSON.stringify(validation, null, 2)}`, {
|
|
|
3440
3440
|
}
|
|
3441
3441
|
});
|
|
3442
3442
|
|
|
3443
|
+
function toBase36(num) {
|
|
3444
|
+
return num.toString(36);
|
|
3445
|
+
}
|
|
3446
|
+
function generateBase36Mapping(keys) {
|
|
3447
|
+
const mapping = {};
|
|
3448
|
+
const reversedMapping = {};
|
|
3449
|
+
keys.forEach((key, index) => {
|
|
3450
|
+
const base36Key = toBase36(index);
|
|
3451
|
+
mapping[key] = base36Key;
|
|
3452
|
+
reversedMapping[base36Key] = key;
|
|
3453
|
+
});
|
|
3454
|
+
return { mapping, reversedMapping };
|
|
3455
|
+
}
|
|
3443
3456
|
const SchemaActions = {
|
|
3444
3457
|
trim: (value) => value.trim(),
|
|
3445
3458
|
encrypt: (value, { passphrase }) => encrypt(value, passphrase),
|
|
3446
|
-
decrypt: (value, { passphrase }) =>
|
|
3459
|
+
decrypt: async (value, { passphrase }) => {
|
|
3460
|
+
try {
|
|
3461
|
+
const raw = await decrypt(value, passphrase);
|
|
3462
|
+
return raw;
|
|
3463
|
+
} catch (error) {
|
|
3464
|
+
console.warn(`Schema decrypt error: ${error}`, error);
|
|
3465
|
+
return value;
|
|
3466
|
+
}
|
|
3467
|
+
},
|
|
3447
3468
|
toString: (value) => String(value),
|
|
3448
3469
|
fromArray: (value, { separator }) => {
|
|
3449
3470
|
if (value === null || value === void 0 || !Array.isArray(value)) {
|
|
@@ -3534,8 +3555,9 @@ ${JSON.stringify(validation, null, 2)}`, {
|
|
|
3534
3555
|
const leafKeys = Object.keys(flatAttrs).filter((k) => !k.includes("$$"));
|
|
3535
3556
|
const objectKeys = this.extractObjectKeys(this.attributes);
|
|
3536
3557
|
const allKeys = [.../* @__PURE__ */ new Set([...leafKeys, ...objectKeys])];
|
|
3537
|
-
|
|
3538
|
-
this.map =
|
|
3558
|
+
const { mapping, reversedMapping } = generateBase36Mapping(allKeys);
|
|
3559
|
+
this.map = mapping;
|
|
3560
|
+
this.reversedMap = reversedMapping;
|
|
3539
3561
|
}
|
|
3540
3562
|
}
|
|
3541
3563
|
defaultOptions() {
|
|
@@ -8608,7 +8630,6 @@ ${JSON.stringify(validation, null, 2)}`, {
|
|
|
8608
8630
|
function calculateAttributeNamesSize(mappedObject) {
|
|
8609
8631
|
let totalSize = 0;
|
|
8610
8632
|
for (const key of Object.keys(mappedObject)) {
|
|
8611
|
-
if (key === "_v") continue;
|
|
8612
8633
|
totalSize += calculateUTF8Bytes(key);
|
|
8613
8634
|
}
|
|
8614
8635
|
return totalSize;
|
|
@@ -8652,6 +8673,30 @@ ${JSON.stringify(validation, null, 2)}`, {
|
|
|
8652
8673
|
const namesSize = calculateAttributeNamesSize(mappedObject);
|
|
8653
8674
|
return valueTotal + namesSize;
|
|
8654
8675
|
}
|
|
8676
|
+
function getSizeBreakdown(mappedObject) {
|
|
8677
|
+
const valueSizes = calculateAttributeSizes(mappedObject);
|
|
8678
|
+
const namesSize = calculateAttributeNamesSize(mappedObject);
|
|
8679
|
+
const valueTotal = Object.values(valueSizes).reduce((sum, size) => sum + size, 0);
|
|
8680
|
+
const total = valueTotal + namesSize;
|
|
8681
|
+
const sortedAttributes = Object.entries(valueSizes).sort(([, a], [, b]) => b - a).map(([key, size]) => ({
|
|
8682
|
+
attribute: key,
|
|
8683
|
+
size,
|
|
8684
|
+
percentage: (size / total * 100).toFixed(2) + "%"
|
|
8685
|
+
}));
|
|
8686
|
+
return {
|
|
8687
|
+
total,
|
|
8688
|
+
valueSizes,
|
|
8689
|
+
namesSize,
|
|
8690
|
+
valueTotal,
|
|
8691
|
+
breakdown: sortedAttributes,
|
|
8692
|
+
// Add detailed breakdown including names
|
|
8693
|
+
detailedBreakdown: {
|
|
8694
|
+
values: valueTotal,
|
|
8695
|
+
names: namesSize,
|
|
8696
|
+
total
|
|
8697
|
+
}
|
|
8698
|
+
};
|
|
8699
|
+
}
|
|
8655
8700
|
|
|
8656
8701
|
const S3_METADATA_LIMIT_BYTES = 2048;
|
|
8657
8702
|
async function handleInsert$3({ resource, data, mappedData }) {
|
|
@@ -8872,6 +8917,7 @@ ${JSON.stringify(validation, null, 2)}`, {
|
|
|
8872
8917
|
}
|
|
8873
8918
|
return behavior;
|
|
8874
8919
|
}
|
|
8920
|
+
const AVAILABLE_BEHAVIORS = Object.keys(behaviors);
|
|
8875
8921
|
const DEFAULT_BEHAVIOR = "user-management";
|
|
8876
8922
|
|
|
8877
8923
|
class Resource extends EventEmitter {
|
|
@@ -8941,17 +8987,8 @@ ${validation.errors.join("\n")}`);
|
|
|
8941
8987
|
partitions = {},
|
|
8942
8988
|
paranoid = true,
|
|
8943
8989
|
allNestedObjectsOptional = true,
|
|
8944
|
-
hooks = {}
|
|
8945
|
-
options = {}
|
|
8990
|
+
hooks = {}
|
|
8946
8991
|
} = 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
8992
|
this.name = name;
|
|
8956
8993
|
this.client = client;
|
|
8957
8994
|
this.version = version;
|
|
@@ -8960,13 +8997,13 @@ ${validation.errors.join("\n")}`);
|
|
|
8960
8997
|
this.parallelism = parallelism;
|
|
8961
8998
|
this.passphrase = passphrase ?? "secret";
|
|
8962
8999
|
this.config = {
|
|
8963
|
-
cache
|
|
9000
|
+
cache,
|
|
8964
9001
|
hooks,
|
|
8965
|
-
paranoid
|
|
8966
|
-
timestamps
|
|
8967
|
-
partitions
|
|
8968
|
-
autoDecrypt
|
|
8969
|
-
allNestedObjectsOptional
|
|
9002
|
+
paranoid,
|
|
9003
|
+
timestamps,
|
|
9004
|
+
partitions,
|
|
9005
|
+
autoDecrypt,
|
|
9006
|
+
allNestedObjectsOptional
|
|
8970
9007
|
};
|
|
8971
9008
|
this.hooks = {
|
|
8972
9009
|
preInsert: [],
|
|
@@ -8977,39 +9014,7 @@ ${validation.errors.join("\n")}`);
|
|
|
8977
9014
|
afterDelete: []
|
|
8978
9015
|
};
|
|
8979
9016
|
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();
|
|
9017
|
+
this.applyConfiguration();
|
|
9013
9018
|
if (hooks) {
|
|
9014
9019
|
for (const [event, hooksArr] of Object.entries(hooks)) {
|
|
9015
9020
|
if (Array.isArray(hooksArr) && this.hooks[event]) {
|
|
@@ -9048,15 +9053,17 @@ ${validation.errors.join("\n")}`);
|
|
|
9048
9053
|
return exported;
|
|
9049
9054
|
}
|
|
9050
9055
|
/**
|
|
9051
|
-
*
|
|
9052
|
-
*
|
|
9056
|
+
* Apply configuration settings (timestamps, partitions, hooks)
|
|
9057
|
+
* This method ensures that all configuration-dependent features are properly set up
|
|
9053
9058
|
*/
|
|
9054
|
-
|
|
9055
|
-
const oldAttributes = this.attributes;
|
|
9056
|
-
this.attributes = newAttributes;
|
|
9059
|
+
applyConfiguration() {
|
|
9057
9060
|
if (this.config.timestamps) {
|
|
9058
|
-
|
|
9059
|
-
|
|
9061
|
+
if (!this.attributes.createdAt) {
|
|
9062
|
+
this.attributes.createdAt = "string|optional";
|
|
9063
|
+
}
|
|
9064
|
+
if (!this.attributes.updatedAt) {
|
|
9065
|
+
this.attributes.updatedAt = "string|optional";
|
|
9066
|
+
}
|
|
9060
9067
|
if (!this.config.partitions) {
|
|
9061
9068
|
this.config.partitions = {};
|
|
9062
9069
|
}
|
|
@@ -9075,9 +9082,10 @@ ${validation.errors.join("\n")}`);
|
|
|
9075
9082
|
};
|
|
9076
9083
|
}
|
|
9077
9084
|
}
|
|
9085
|
+
this.setupPartitionHooks();
|
|
9078
9086
|
this.schema = new Schema({
|
|
9079
9087
|
name: this.name,
|
|
9080
|
-
attributes:
|
|
9088
|
+
attributes: this.attributes,
|
|
9081
9089
|
passphrase: this.passphrase,
|
|
9082
9090
|
version: this.version,
|
|
9083
9091
|
options: {
|
|
@@ -9085,8 +9093,16 @@ ${validation.errors.join("\n")}`);
|
|
|
9085
9093
|
allNestedObjectsOptional: this.config.allNestedObjectsOptional
|
|
9086
9094
|
}
|
|
9087
9095
|
});
|
|
9088
|
-
this.setupPartitionHooks();
|
|
9089
9096
|
this.validatePartitions();
|
|
9097
|
+
}
|
|
9098
|
+
/**
|
|
9099
|
+
* Update resource attributes and rebuild schema
|
|
9100
|
+
* @param {Object} newAttributes - New attributes definition
|
|
9101
|
+
*/
|
|
9102
|
+
updateAttributes(newAttributes) {
|
|
9103
|
+
const oldAttributes = this.attributes;
|
|
9104
|
+
this.attributes = newAttributes;
|
|
9105
|
+
this.applyConfiguration();
|
|
9090
9106
|
return { oldAttributes, newAttributes };
|
|
9091
9107
|
}
|
|
9092
9108
|
/**
|
|
@@ -9174,7 +9190,7 @@ ${validation.errors.join("\n")}`);
|
|
|
9174
9190
|
for (const fieldName of Object.keys(partitionDef.fields)) {
|
|
9175
9191
|
if (!this.fieldExistsInAttributes(fieldName)) {
|
|
9176
9192
|
throw new Error(
|
|
9177
|
-
`Partition '${partitionName}' uses field '${fieldName}' which does not exist in resource
|
|
9193
|
+
`Partition '${partitionName}' uses field '${fieldName}' which does not exist in resource attributes. Available fields: ${currentAttributes.join(", ")}.`
|
|
9178
9194
|
);
|
|
9179
9195
|
}
|
|
9180
9196
|
}
|
|
@@ -9287,7 +9303,11 @@ ${validation.errors.join("\n")}`);
|
|
|
9287
9303
|
if (partitionSegments.length === 0) {
|
|
9288
9304
|
return null;
|
|
9289
9305
|
}
|
|
9290
|
-
|
|
9306
|
+
const finalId = id || data?.id;
|
|
9307
|
+
if (!finalId) {
|
|
9308
|
+
return null;
|
|
9309
|
+
}
|
|
9310
|
+
return join(`resource=${this.name}`, `partition=${partitionName}`, ...partitionSegments, `id=${finalId}`);
|
|
9291
9311
|
}
|
|
9292
9312
|
/**
|
|
9293
9313
|
* Get nested field value from data object using dot notation
|
|
@@ -9516,12 +9536,12 @@ ${validation.errors.join("\n")}`);
|
|
|
9516
9536
|
* console.log(updatedUser.updatedAt); // ISO timestamp
|
|
9517
9537
|
*/
|
|
9518
9538
|
async update(id, attributes) {
|
|
9519
|
-
const
|
|
9539
|
+
const originalData = await this.get(id);
|
|
9520
9540
|
if (this.config.timestamps) {
|
|
9521
9541
|
attributes.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9522
9542
|
}
|
|
9523
9543
|
const preProcessedData = await this.executeHooks("preUpdate", attributes);
|
|
9524
|
-
const attrs = lodashEs.merge(
|
|
9544
|
+
const attrs = lodashEs.merge(originalData, preProcessedData);
|
|
9525
9545
|
delete attrs.id;
|
|
9526
9546
|
const { isValid, errors, data: validated } = await this.validate(attrs);
|
|
9527
9547
|
if (!isValid) {
|
|
@@ -9532,6 +9552,9 @@ ${validation.errors.join("\n")}`);
|
|
|
9532
9552
|
validation: errors
|
|
9533
9553
|
});
|
|
9534
9554
|
}
|
|
9555
|
+
const oldData = { ...originalData, id };
|
|
9556
|
+
const newData = { ...validated, id };
|
|
9557
|
+
await this.handlePartitionReferenceUpdates(oldData, newData);
|
|
9535
9558
|
const mappedData = await this.schema.mapper(validated);
|
|
9536
9559
|
const behaviorImpl = getBehavior(this.behavior);
|
|
9537
9560
|
const { mappedData: processedMetadata, body } = await behaviorImpl.handleUpdate({
|
|
@@ -9567,7 +9590,6 @@ ${validation.errors.join("\n")}`);
|
|
|
9567
9590
|
});
|
|
9568
9591
|
validated.id = id;
|
|
9569
9592
|
await this.executeHooks("afterUpdate", validated);
|
|
9570
|
-
await this.updatePartitionReferences(validated);
|
|
9571
9593
|
this.emit("update", preProcessedData, validated);
|
|
9572
9594
|
return validated;
|
|
9573
9595
|
}
|
|
@@ -9854,23 +9876,104 @@ ${validation.errors.join("\n")}`);
|
|
|
9854
9876
|
* });
|
|
9855
9877
|
*/
|
|
9856
9878
|
async list({ partition = null, partitionValues = {}, limit, offset = 0 } = {}) {
|
|
9857
|
-
|
|
9858
|
-
|
|
9859
|
-
|
|
9879
|
+
try {
|
|
9880
|
+
if (!partition) {
|
|
9881
|
+
let ids2 = [];
|
|
9882
|
+
try {
|
|
9883
|
+
ids2 = await this.listIds({ partition, partitionValues });
|
|
9884
|
+
} catch (listIdsError) {
|
|
9885
|
+
console.warn(`Failed to get list IDs:`, listIdsError.message);
|
|
9886
|
+
return [];
|
|
9887
|
+
}
|
|
9888
|
+
let filteredIds2 = ids2.slice(offset);
|
|
9889
|
+
if (limit) {
|
|
9890
|
+
filteredIds2 = filteredIds2.slice(0, limit);
|
|
9891
|
+
}
|
|
9892
|
+
const { results: results2, errors: errors2 } = await promisePool.PromisePool.for(filteredIds2).withConcurrency(this.parallelism).handleError(async (error, id) => {
|
|
9893
|
+
console.warn(`Failed to get resource ${id}:`, error.message);
|
|
9894
|
+
return null;
|
|
9895
|
+
}).process(async (id) => {
|
|
9896
|
+
try {
|
|
9897
|
+
return await this.get(id);
|
|
9898
|
+
} catch (error) {
|
|
9899
|
+
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9900
|
+
console.warn(`Decryption failed for ${id}, returning basic info`);
|
|
9901
|
+
return {
|
|
9902
|
+
id,
|
|
9903
|
+
_decryptionFailed: true,
|
|
9904
|
+
_error: error.message
|
|
9905
|
+
};
|
|
9906
|
+
}
|
|
9907
|
+
throw error;
|
|
9908
|
+
}
|
|
9909
|
+
});
|
|
9910
|
+
const validResults2 = results2.filter((item) => item !== null);
|
|
9911
|
+
this.emit("list", { partition, partitionValues, count: validResults2.length, errors: errors2.length });
|
|
9912
|
+
return validResults2;
|
|
9913
|
+
}
|
|
9914
|
+
if (!this.config.partitions || !this.config.partitions[partition]) {
|
|
9915
|
+
console.warn(`Partition '${partition}' not found in resource '${this.name}'`);
|
|
9916
|
+
this.emit("list", { partition, partitionValues, count: 0, errors: 0 });
|
|
9917
|
+
return [];
|
|
9918
|
+
}
|
|
9919
|
+
const partitionDef = this.config.partitions[partition];
|
|
9920
|
+
const partitionSegments = [];
|
|
9921
|
+
const sortedFields = Object.entries(partitionDef.fields).sort(([a], [b]) => a.localeCompare(b));
|
|
9922
|
+
for (const [fieldName, rule] of sortedFields) {
|
|
9923
|
+
const value = partitionValues[fieldName];
|
|
9924
|
+
if (value !== void 0 && value !== null) {
|
|
9925
|
+
const transformedValue = this.applyPartitionRule(value, rule);
|
|
9926
|
+
partitionSegments.push(`${fieldName}=${transformedValue}`);
|
|
9927
|
+
}
|
|
9928
|
+
}
|
|
9929
|
+
let prefix;
|
|
9930
|
+
if (partitionSegments.length > 0) {
|
|
9931
|
+
prefix = `resource=${this.name}/partition=${partition}/${partitionSegments.join("/")}`;
|
|
9932
|
+
} else {
|
|
9933
|
+
prefix = `resource=${this.name}/partition=${partition}`;
|
|
9934
|
+
}
|
|
9935
|
+
let keys = [];
|
|
9936
|
+
try {
|
|
9937
|
+
keys = await this.client.getAllKeys({ prefix });
|
|
9938
|
+
} catch (getKeysError) {
|
|
9939
|
+
console.warn(`Failed to get partition keys:`, getKeysError.message);
|
|
9940
|
+
return [];
|
|
9941
|
+
}
|
|
9942
|
+
const ids = keys.map((key) => {
|
|
9943
|
+
const parts = key.split("/");
|
|
9944
|
+
const idPart = parts.find((part) => part.startsWith("id="));
|
|
9945
|
+
return idPart ? idPart.replace("id=", "") : null;
|
|
9946
|
+
}).filter(Boolean);
|
|
9947
|
+
let filteredIds = ids.slice(offset);
|
|
9860
9948
|
if (limit) {
|
|
9861
|
-
|
|
9949
|
+
filteredIds = filteredIds.slice(0, limit);
|
|
9862
9950
|
}
|
|
9863
|
-
const { results
|
|
9864
|
-
console.warn(`Failed to get resource ${id}:`, error.message);
|
|
9951
|
+
const { results, errors } = await promisePool.PromisePool.for(filteredIds).withConcurrency(this.parallelism).handleError(async (error, id) => {
|
|
9952
|
+
console.warn(`Failed to get partition resource ${id}:`, error.message);
|
|
9865
9953
|
return null;
|
|
9866
9954
|
}).process(async (id) => {
|
|
9867
9955
|
try {
|
|
9868
|
-
|
|
9956
|
+
const keyForId = keys.find((key) => key.includes(`id=${id}`));
|
|
9957
|
+
if (!keyForId) {
|
|
9958
|
+
throw new Error(`Partition key not found for ID ${id}`);
|
|
9959
|
+
}
|
|
9960
|
+
const keyParts = keyForId.split("/");
|
|
9961
|
+
const actualPartitionValues = {};
|
|
9962
|
+
for (const [fieldName, rule] of sortedFields) {
|
|
9963
|
+
const fieldPart = keyParts.find((part) => part.startsWith(`${fieldName}=`));
|
|
9964
|
+
if (fieldPart) {
|
|
9965
|
+
const value = fieldPart.replace(`${fieldName}=`, "");
|
|
9966
|
+
actualPartitionValues[fieldName] = value;
|
|
9967
|
+
}
|
|
9968
|
+
}
|
|
9969
|
+
return await this.getFromPartition({ id, partitionName: partition, partitionValues: actualPartitionValues });
|
|
9869
9970
|
} catch (error) {
|
|
9870
9971
|
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9871
|
-
console.warn(`Decryption failed for ${id}, returning basic info`);
|
|
9972
|
+
console.warn(`Decryption failed for partition resource ${id}, returning basic info`);
|
|
9872
9973
|
return {
|
|
9873
9974
|
id,
|
|
9975
|
+
_partition: partition,
|
|
9976
|
+
_partitionValues: partitionValues,
|
|
9874
9977
|
_decryptionFailed: true,
|
|
9875
9978
|
_error: error.message
|
|
9876
9979
|
};
|
|
@@ -9878,62 +9981,19 @@ ${validation.errors.join("\n")}`);
|
|
|
9878
9981
|
throw error;
|
|
9879
9982
|
}
|
|
9880
9983
|
});
|
|
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}`);
|
|
9984
|
+
const validResults = results.filter((item) => item !== null);
|
|
9985
|
+
this.emit("list", { partition, partitionValues, count: validResults.length, errors: errors.length });
|
|
9986
|
+
return validResults;
|
|
9987
|
+
} catch (error) {
|
|
9988
|
+
if (error.message.includes("Partition '") && error.message.includes("' not found")) {
|
|
9989
|
+
console.warn(`Partition error in list method:`, error.message);
|
|
9990
|
+
this.emit("list", { partition, partitionValues, count: 0, errors: 1 });
|
|
9991
|
+
return [];
|
|
9896
9992
|
}
|
|
9993
|
+
console.error(`Critical error in list method:`, error.message);
|
|
9994
|
+
this.emit("list", { partition, partitionValues, count: 0, errors: 1 });
|
|
9995
|
+
return [];
|
|
9897
9996
|
}
|
|
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
9997
|
}
|
|
9938
9998
|
/**
|
|
9939
9999
|
* Get multiple resources by their IDs
|
|
@@ -10040,36 +10100,67 @@ ${validation.errors.join("\n")}`);
|
|
|
10040
10100
|
* console.log(`Got ${fastPage.items.length} items`); // totalItems will be null
|
|
10041
10101
|
*/
|
|
10042
10102
|
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
|
|
10103
|
+
try {
|
|
10104
|
+
let totalItems = null;
|
|
10105
|
+
let totalPages = null;
|
|
10106
|
+
if (!skipCount) {
|
|
10107
|
+
try {
|
|
10108
|
+
totalItems = await this.count({ partition, partitionValues });
|
|
10109
|
+
totalPages = Math.ceil(totalItems / size);
|
|
10110
|
+
} catch (countError) {
|
|
10111
|
+
console.warn(`Failed to get count for page:`, countError.message);
|
|
10112
|
+
totalItems = null;
|
|
10113
|
+
totalPages = null;
|
|
10114
|
+
}
|
|
10069
10115
|
}
|
|
10070
|
-
|
|
10071
|
-
|
|
10072
|
-
|
|
10116
|
+
const page = Math.floor(offset / size);
|
|
10117
|
+
let items = [];
|
|
10118
|
+
try {
|
|
10119
|
+
items = await this.list({
|
|
10120
|
+
partition,
|
|
10121
|
+
partitionValues,
|
|
10122
|
+
limit: size,
|
|
10123
|
+
offset
|
|
10124
|
+
});
|
|
10125
|
+
} catch (listError) {
|
|
10126
|
+
console.warn(`Failed to get items for page:`, listError.message);
|
|
10127
|
+
items = [];
|
|
10128
|
+
}
|
|
10129
|
+
const result = {
|
|
10130
|
+
items,
|
|
10131
|
+
totalItems,
|
|
10132
|
+
page,
|
|
10133
|
+
pageSize: size,
|
|
10134
|
+
totalPages,
|
|
10135
|
+
// Add additional metadata for debugging
|
|
10136
|
+
_debug: {
|
|
10137
|
+
requestedSize: size,
|
|
10138
|
+
requestedOffset: offset,
|
|
10139
|
+
actualItemsReturned: items.length,
|
|
10140
|
+
skipCount,
|
|
10141
|
+
hasTotalItems: totalItems !== null
|
|
10142
|
+
}
|
|
10143
|
+
};
|
|
10144
|
+
this.emit("page", result);
|
|
10145
|
+
return result;
|
|
10146
|
+
} catch (error) {
|
|
10147
|
+
console.error(`Critical error in page method:`, error.message);
|
|
10148
|
+
return {
|
|
10149
|
+
items: [],
|
|
10150
|
+
totalItems: null,
|
|
10151
|
+
page: Math.floor(offset / size),
|
|
10152
|
+
pageSize: size,
|
|
10153
|
+
totalPages: null,
|
|
10154
|
+
_debug: {
|
|
10155
|
+
requestedSize: size,
|
|
10156
|
+
requestedOffset: offset,
|
|
10157
|
+
actualItemsReturned: 0,
|
|
10158
|
+
skipCount,
|
|
10159
|
+
hasTotalItems: false,
|
|
10160
|
+
error: error.message
|
|
10161
|
+
}
|
|
10162
|
+
};
|
|
10163
|
+
}
|
|
10073
10164
|
}
|
|
10074
10165
|
readable() {
|
|
10075
10166
|
const stream = new ResourceReader({ resource: this });
|
|
@@ -10362,7 +10453,118 @@ ${validation.errors.join("\n")}`);
|
|
|
10362
10453
|
return results.slice(0, limit);
|
|
10363
10454
|
}
|
|
10364
10455
|
/**
|
|
10365
|
-
*
|
|
10456
|
+
* Handle partition reference updates with change detection
|
|
10457
|
+
* @param {Object} oldData - Original object data before update
|
|
10458
|
+
* @param {Object} newData - Updated object data
|
|
10459
|
+
*/
|
|
10460
|
+
async handlePartitionReferenceUpdates(oldData, newData) {
|
|
10461
|
+
const partitions = this.config.partitions;
|
|
10462
|
+
if (!partitions || Object.keys(partitions).length === 0) {
|
|
10463
|
+
return;
|
|
10464
|
+
}
|
|
10465
|
+
for (const [partitionName, partition] of Object.entries(partitions)) {
|
|
10466
|
+
try {
|
|
10467
|
+
await this.handlePartitionReferenceUpdate(partitionName, partition, oldData, newData);
|
|
10468
|
+
} catch (error) {
|
|
10469
|
+
console.warn(`Failed to update partition references for ${partitionName}:`, error.message);
|
|
10470
|
+
}
|
|
10471
|
+
}
|
|
10472
|
+
const id = newData.id || oldData.id;
|
|
10473
|
+
for (const [partitionName, partition] of Object.entries(partitions)) {
|
|
10474
|
+
const prefix = `resource=${this.name}/partition=${partitionName}`;
|
|
10475
|
+
let allKeys = [];
|
|
10476
|
+
try {
|
|
10477
|
+
allKeys = await this.client.getAllKeys({ prefix });
|
|
10478
|
+
} catch (error) {
|
|
10479
|
+
console.warn(`Aggressive cleanup: could not list keys for partition ${partitionName}:`, error.message);
|
|
10480
|
+
continue;
|
|
10481
|
+
}
|
|
10482
|
+
const validKey = this.getPartitionKey({ partitionName, id, data: newData });
|
|
10483
|
+
for (const key of allKeys) {
|
|
10484
|
+
if (key.endsWith(`/id=${id}`) && key !== validKey) {
|
|
10485
|
+
try {
|
|
10486
|
+
await this.client.deleteObject(key);
|
|
10487
|
+
} catch (error) {
|
|
10488
|
+
console.warn(`Aggressive cleanup: could not delete stale partition key ${key}:`, error.message);
|
|
10489
|
+
}
|
|
10490
|
+
}
|
|
10491
|
+
}
|
|
10492
|
+
}
|
|
10493
|
+
}
|
|
10494
|
+
/**
|
|
10495
|
+
* Handle partition reference update for a specific partition
|
|
10496
|
+
* @param {string} partitionName - Name of the partition
|
|
10497
|
+
* @param {Object} partition - Partition definition
|
|
10498
|
+
* @param {Object} oldData - Original object data before update
|
|
10499
|
+
* @param {Object} newData - Updated object data
|
|
10500
|
+
*/
|
|
10501
|
+
async handlePartitionReferenceUpdate(partitionName, partition, oldData, newData) {
|
|
10502
|
+
const id = newData.id || oldData.id;
|
|
10503
|
+
const oldPartitionKey = this.getPartitionKey({ partitionName, id, data: oldData });
|
|
10504
|
+
const newPartitionKey = this.getPartitionKey({ partitionName, id, data: newData });
|
|
10505
|
+
if (oldPartitionKey !== newPartitionKey) {
|
|
10506
|
+
if (oldPartitionKey) {
|
|
10507
|
+
try {
|
|
10508
|
+
await this.client.deleteObject(oldPartitionKey);
|
|
10509
|
+
} catch (error) {
|
|
10510
|
+
console.warn(`Old partition object could not be deleted for ${partitionName}:`, error.message);
|
|
10511
|
+
}
|
|
10512
|
+
}
|
|
10513
|
+
if (newPartitionKey) {
|
|
10514
|
+
try {
|
|
10515
|
+
const mappedData = await this.schema.mapper(newData);
|
|
10516
|
+
if (mappedData.undefined !== void 0) delete mappedData.undefined;
|
|
10517
|
+
const behaviorImpl = getBehavior(this.behavior);
|
|
10518
|
+
const { mappedData: processedMetadata, body } = await behaviorImpl.handleUpdate({
|
|
10519
|
+
resource: this,
|
|
10520
|
+
id,
|
|
10521
|
+
data: newData,
|
|
10522
|
+
mappedData
|
|
10523
|
+
});
|
|
10524
|
+
if (processedMetadata.undefined !== void 0) delete processedMetadata.undefined;
|
|
10525
|
+
const partitionMetadata = {
|
|
10526
|
+
...processedMetadata,
|
|
10527
|
+
_version: this.version
|
|
10528
|
+
};
|
|
10529
|
+
if (partitionMetadata.undefined !== void 0) delete partitionMetadata.undefined;
|
|
10530
|
+
await this.client.putObject({
|
|
10531
|
+
key: newPartitionKey,
|
|
10532
|
+
metadata: partitionMetadata,
|
|
10533
|
+
body
|
|
10534
|
+
});
|
|
10535
|
+
} catch (error) {
|
|
10536
|
+
console.warn(`New partition object could not be created for ${partitionName}:`, error.message);
|
|
10537
|
+
}
|
|
10538
|
+
}
|
|
10539
|
+
} else if (newPartitionKey) {
|
|
10540
|
+
try {
|
|
10541
|
+
const mappedData = await this.schema.mapper(newData);
|
|
10542
|
+
if (mappedData.undefined !== void 0) delete mappedData.undefined;
|
|
10543
|
+
const behaviorImpl = getBehavior(this.behavior);
|
|
10544
|
+
const { mappedData: processedMetadata, body } = await behaviorImpl.handleUpdate({
|
|
10545
|
+
resource: this,
|
|
10546
|
+
id,
|
|
10547
|
+
data: newData,
|
|
10548
|
+
mappedData
|
|
10549
|
+
});
|
|
10550
|
+
if (processedMetadata.undefined !== void 0) delete processedMetadata.undefined;
|
|
10551
|
+
const partitionMetadata = {
|
|
10552
|
+
...processedMetadata,
|
|
10553
|
+
_version: this.version
|
|
10554
|
+
};
|
|
10555
|
+
if (partitionMetadata.undefined !== void 0) delete partitionMetadata.undefined;
|
|
10556
|
+
await this.client.putObject({
|
|
10557
|
+
key: newPartitionKey,
|
|
10558
|
+
metadata: partitionMetadata,
|
|
10559
|
+
body
|
|
10560
|
+
});
|
|
10561
|
+
} catch (error) {
|
|
10562
|
+
console.warn(`Partition object could not be updated for ${partitionName}:`, error.message);
|
|
10563
|
+
}
|
|
10564
|
+
}
|
|
10565
|
+
}
|
|
10566
|
+
/**
|
|
10567
|
+
* Update partition objects to keep them in sync (legacy method for backward compatibility)
|
|
10366
10568
|
* @param {Object} data - Updated object data
|
|
10367
10569
|
*/
|
|
10368
10570
|
async updatePartitionReferences(data) {
|
|
@@ -10371,6 +10573,10 @@ ${validation.errors.join("\n")}`);
|
|
|
10371
10573
|
return;
|
|
10372
10574
|
}
|
|
10373
10575
|
for (const [partitionName, partition] of Object.entries(partitions)) {
|
|
10576
|
+
if (!partition || !partition.fields || typeof partition.fields !== "object") {
|
|
10577
|
+
console.warn(`Skipping invalid partition '${partitionName}' in resource '${this.name}'`);
|
|
10578
|
+
continue;
|
|
10579
|
+
}
|
|
10374
10580
|
const partitionKey = this.getPartitionKey({ partitionName, id: data.id, data });
|
|
10375
10581
|
if (partitionKey) {
|
|
10376
10582
|
const mappedData = await this.schema.mapper(data);
|
|
@@ -10469,6 +10675,7 @@ ${validation.errors.join("\n")}`);
|
|
|
10469
10675
|
if (request.VersionId) data._versionId = request.VersionId;
|
|
10470
10676
|
if (request.Expiration) data._expiresAt = request.Expiration;
|
|
10471
10677
|
data._definitionHash = this.getDefinitionHash();
|
|
10678
|
+
if (data.undefined !== void 0) delete data.undefined;
|
|
10472
10679
|
this.emit("getFromPartition", data);
|
|
10473
10680
|
return data;
|
|
10474
10681
|
}
|
|
@@ -10572,7 +10779,7 @@ ${validation.errors.join("\n")}`);
|
|
|
10572
10779
|
this.version = "1";
|
|
10573
10780
|
this.s3dbVersion = (() => {
|
|
10574
10781
|
try {
|
|
10575
|
-
return true ? "
|
|
10782
|
+
return true ? "5.0.0" : "latest";
|
|
10576
10783
|
} catch (e) {
|
|
10577
10784
|
return "latest";
|
|
10578
10785
|
}
|
|
@@ -10587,14 +10794,24 @@ ${validation.errors.join("\n")}`);
|
|
|
10587
10794
|
this.passphrase = options.passphrase || "secret";
|
|
10588
10795
|
let connectionString = options.connectionString;
|
|
10589
10796
|
if (!connectionString && (options.bucket || options.accessKeyId || options.secretAccessKey)) {
|
|
10590
|
-
|
|
10591
|
-
|
|
10592
|
-
|
|
10593
|
-
accessKeyId
|
|
10594
|
-
secretAccessKey
|
|
10595
|
-
|
|
10596
|
-
forcePathStyle
|
|
10597
|
-
|
|
10797
|
+
const { bucket, region, accessKeyId, secretAccessKey, endpoint, forcePathStyle } = options;
|
|
10798
|
+
if (endpoint) {
|
|
10799
|
+
const url = new URL(endpoint);
|
|
10800
|
+
if (accessKeyId) url.username = encodeURIComponent(accessKeyId);
|
|
10801
|
+
if (secretAccessKey) url.password = encodeURIComponent(secretAccessKey);
|
|
10802
|
+
url.pathname = `/${bucket || "s3db"}`;
|
|
10803
|
+
if (forcePathStyle) {
|
|
10804
|
+
url.searchParams.set("forcePathStyle", "true");
|
|
10805
|
+
}
|
|
10806
|
+
connectionString = url.toString();
|
|
10807
|
+
} else if (accessKeyId && secretAccessKey) {
|
|
10808
|
+
const params = new URLSearchParams();
|
|
10809
|
+
params.set("region", region || "us-east-1");
|
|
10810
|
+
if (forcePathStyle) {
|
|
10811
|
+
params.set("forcePathStyle", "true");
|
|
10812
|
+
}
|
|
10813
|
+
connectionString = `s3://${encodeURIComponent(accessKeyId)}:${encodeURIComponent(secretAccessKey)}@${bucket || "s3db"}?${params.toString()}`;
|
|
10814
|
+
}
|
|
10598
10815
|
}
|
|
10599
10816
|
this.client = options.client || new Client({
|
|
10600
10817
|
verbose: this.verbose,
|
|
@@ -10630,12 +10847,12 @@ ${validation.errors.join("\n")}`);
|
|
|
10630
10847
|
passphrase: this.passphrase,
|
|
10631
10848
|
observers: [this],
|
|
10632
10849
|
cache: this.cache,
|
|
10633
|
-
timestamps: versionData.
|
|
10634
|
-
partitions: resourceMetadata.partitions || versionData.
|
|
10635
|
-
paranoid: versionData.
|
|
10636
|
-
allNestedObjectsOptional: versionData.
|
|
10637
|
-
autoDecrypt: versionData.
|
|
10638
|
-
hooks: {}
|
|
10850
|
+
timestamps: versionData.timestamps !== void 0 ? versionData.timestamps : false,
|
|
10851
|
+
partitions: resourceMetadata.partitions || versionData.partitions || {},
|
|
10852
|
+
paranoid: versionData.paranoid !== void 0 ? versionData.paranoid : true,
|
|
10853
|
+
allNestedObjectsOptional: versionData.allNestedObjectsOptional !== void 0 ? versionData.allNestedObjectsOptional : true,
|
|
10854
|
+
autoDecrypt: versionData.autoDecrypt !== void 0 ? versionData.autoDecrypt : true,
|
|
10855
|
+
hooks: versionData.hooks || {}
|
|
10639
10856
|
});
|
|
10640
10857
|
}
|
|
10641
10858
|
}
|
|
@@ -10773,15 +10990,14 @@ ${validation.errors.join("\n")}`);
|
|
|
10773
10990
|
[version]: {
|
|
10774
10991
|
hash: definitionHash,
|
|
10775
10992
|
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
10993
|
behavior: resourceDef.behavior || "user-management",
|
|
10994
|
+
timestamps: resource.config.timestamps,
|
|
10995
|
+
partitions: resource.config.partitions,
|
|
10996
|
+
paranoid: resource.config.paranoid,
|
|
10997
|
+
allNestedObjectsOptional: resource.config.allNestedObjectsOptional,
|
|
10998
|
+
autoDecrypt: resource.config.autoDecrypt,
|
|
10999
|
+
cache: resource.config.cache,
|
|
11000
|
+
hooks: resource.config.hooks,
|
|
10785
11001
|
createdAt: isNewVersion ? (/* @__PURE__ */ new Date()).toISOString() : existingVersionData?.createdAt
|
|
10786
11002
|
}
|
|
10787
11003
|
}
|
|
@@ -10816,73 +11032,58 @@ ${validation.errors.join("\n")}`);
|
|
|
10816
11032
|
}
|
|
10817
11033
|
/**
|
|
10818
11034
|
* Check if a resource exists with the same definition hash
|
|
10819
|
-
* @param {
|
|
10820
|
-
* @param {
|
|
10821
|
-
* @param {Object}
|
|
10822
|
-
* @param {string} behavior - Resource behavior
|
|
10823
|
-
* @
|
|
11035
|
+
* @param {Object} config - Resource configuration
|
|
11036
|
+
* @param {string} config.name - Resource name
|
|
11037
|
+
* @param {Object} config.attributes - Resource attributes
|
|
11038
|
+
* @param {string} [config.behavior] - Resource behavior
|
|
11039
|
+
* @param {Object} [config.options] - Resource options (deprecated, use root level parameters)
|
|
11040
|
+
* @returns {Object} Result with exists and hash information
|
|
10824
11041
|
*/
|
|
10825
|
-
resourceExistsWithSameHash({ name, attributes,
|
|
11042
|
+
resourceExistsWithSameHash({ name, attributes, behavior = "user-management", options = {} }) {
|
|
10826
11043
|
if (!this.resources[name]) {
|
|
10827
11044
|
return { exists: false, sameHash: false, hash: null };
|
|
10828
11045
|
}
|
|
10829
|
-
const
|
|
11046
|
+
const existingResource = this.resources[name];
|
|
11047
|
+
const existingHash = this.generateDefinitionHash(existingResource.export());
|
|
11048
|
+
const mockResource = new Resource({
|
|
10830
11049
|
name,
|
|
10831
11050
|
attributes,
|
|
10832
11051
|
behavior,
|
|
10833
|
-
observers: [],
|
|
10834
11052
|
client: this.client,
|
|
10835
|
-
version:
|
|
11053
|
+
version: existingResource.version,
|
|
10836
11054
|
passphrase: this.passphrase,
|
|
10837
|
-
cache: this.cache,
|
|
10838
11055
|
...options
|
|
10839
11056
|
});
|
|
10840
|
-
const newHash = this.generateDefinitionHash(
|
|
10841
|
-
const existingHash = this.generateDefinitionHash(this.resources[name].export(), this.resources[name].behavior);
|
|
11057
|
+
const newHash = this.generateDefinitionHash(mockResource.export());
|
|
10842
11058
|
return {
|
|
10843
11059
|
exists: true,
|
|
10844
|
-
sameHash:
|
|
11060
|
+
sameHash: existingHash === newHash,
|
|
10845
11061
|
hash: newHash,
|
|
10846
11062
|
existingHash
|
|
10847
11063
|
};
|
|
10848
11064
|
}
|
|
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" }) {
|
|
11065
|
+
async createResource({ name, attributes, behavior = "user-management", hooks, ...config }) {
|
|
10876
11066
|
if (this.resources[name]) {
|
|
10877
11067
|
const existingResource = this.resources[name];
|
|
10878
11068
|
Object.assign(existingResource.config, {
|
|
10879
11069
|
cache: this.cache,
|
|
10880
|
-
...
|
|
11070
|
+
...config
|
|
10881
11071
|
});
|
|
10882
11072
|
if (behavior) {
|
|
10883
11073
|
existingResource.behavior = behavior;
|
|
10884
11074
|
}
|
|
10885
11075
|
existingResource.updateAttributes(attributes);
|
|
11076
|
+
if (hooks) {
|
|
11077
|
+
for (const [event, hooksArr] of Object.entries(hooks)) {
|
|
11078
|
+
if (Array.isArray(hooksArr) && existingResource.hooks[event]) {
|
|
11079
|
+
for (const fn of hooksArr) {
|
|
11080
|
+
if (typeof fn === "function") {
|
|
11081
|
+
existingResource.hooks[event].push(fn.bind(existingResource));
|
|
11082
|
+
}
|
|
11083
|
+
}
|
|
11084
|
+
}
|
|
11085
|
+
}
|
|
11086
|
+
}
|
|
10886
11087
|
const newHash = this.generateDefinitionHash(existingResource.export(), existingResource.behavior);
|
|
10887
11088
|
const existingMetadata2 = this.savedMetadata?.resources?.[name];
|
|
10888
11089
|
const currentVersion = existingMetadata2?.currentVersion || "v0";
|
|
@@ -10904,7 +11105,8 @@ ${validation.errors.join("\n")}`);
|
|
|
10904
11105
|
version,
|
|
10905
11106
|
passphrase: this.passphrase,
|
|
10906
11107
|
cache: this.cache,
|
|
10907
|
-
|
|
11108
|
+
hooks,
|
|
11109
|
+
...config
|
|
10908
11110
|
});
|
|
10909
11111
|
this.resources[name] = resource;
|
|
10910
11112
|
await this.uploadMetadataFile();
|
|
@@ -17606,6 +17808,7 @@ ${validation.errors.join("\n")}`);
|
|
|
17606
17808
|
}
|
|
17607
17809
|
}
|
|
17608
17810
|
|
|
17811
|
+
exports.AVAILABLE_BEHAVIORS = AVAILABLE_BEHAVIORS;
|
|
17609
17812
|
exports.AuthenticationError = AuthenticationError;
|
|
17610
17813
|
exports.BaseError = BaseError;
|
|
17611
17814
|
exports.Cache = Cache;
|
|
@@ -17613,6 +17816,7 @@ ${validation.errors.join("\n")}`);
|
|
|
17613
17816
|
exports.Client = Client;
|
|
17614
17817
|
exports.ConnectionString = ConnectionString;
|
|
17615
17818
|
exports.CostsPlugin = CostsPlugin;
|
|
17819
|
+
exports.DEFAULT_BEHAVIOR = DEFAULT_BEHAVIOR;
|
|
17616
17820
|
exports.Database = Database;
|
|
17617
17821
|
exports.DatabaseError = DatabaseError;
|
|
17618
17822
|
exports.EncryptionError = EncryptionError;
|
|
@@ -17626,28 +17830,38 @@ ${validation.errors.join("\n")}`);
|
|
|
17626
17830
|
exports.PermissionError = PermissionError;
|
|
17627
17831
|
exports.Plugin = Plugin;
|
|
17628
17832
|
exports.PluginObject = PluginObject;
|
|
17833
|
+
exports.Resource = Resource;
|
|
17629
17834
|
exports.ResourceIdsPageReader = ResourceIdsPageReader;
|
|
17630
17835
|
exports.ResourceIdsReader = ResourceIdsReader;
|
|
17631
17836
|
exports.ResourceNotFound = ResourceNotFound;
|
|
17632
|
-
exports.ResourceNotFoundError = ResourceNotFound;
|
|
17633
17837
|
exports.ResourceReader = ResourceReader;
|
|
17634
17838
|
exports.ResourceWriter = ResourceWriter;
|
|
17635
17839
|
exports.S3Cache = S3Cache;
|
|
17636
|
-
exports.S3DB = S3db;
|
|
17637
17840
|
exports.S3DBError = S3DBError;
|
|
17638
17841
|
exports.S3_DEFAULT_ENDPOINT = S3_DEFAULT_ENDPOINT;
|
|
17639
17842
|
exports.S3_DEFAULT_REGION = S3_DEFAULT_REGION;
|
|
17640
17843
|
exports.S3db = S3db;
|
|
17641
|
-
exports.
|
|
17844
|
+
exports.Schema = Schema;
|
|
17845
|
+
exports.SchemaActions = SchemaActions;
|
|
17642
17846
|
exports.UnknownError = UnknownError;
|
|
17643
17847
|
exports.ValidationError = ValidationError;
|
|
17644
17848
|
exports.Validator = Validator;
|
|
17645
17849
|
exports.ValidatorManager = ValidatorManager;
|
|
17850
|
+
exports.behaviors = behaviors;
|
|
17851
|
+
exports.calculateAttributeNamesSize = calculateAttributeNamesSize;
|
|
17852
|
+
exports.calculateAttributeSizes = calculateAttributeSizes;
|
|
17853
|
+
exports.calculateTotalSize = calculateTotalSize;
|
|
17854
|
+
exports.calculateUTF8Bytes = calculateUTF8Bytes;
|
|
17646
17855
|
exports.decrypt = decrypt;
|
|
17647
17856
|
exports.default = S3db;
|
|
17648
17857
|
exports.encrypt = encrypt;
|
|
17858
|
+
exports.getBehavior = getBehavior;
|
|
17859
|
+
exports.getSizeBreakdown = getSizeBreakdown;
|
|
17860
|
+
exports.idGenerator = idGenerator;
|
|
17861
|
+
exports.passwordGenerator = passwordGenerator;
|
|
17649
17862
|
exports.sha256 = sha256;
|
|
17650
17863
|
exports.streamToString = streamToString;
|
|
17864
|
+
exports.transformValue = transformValue;
|
|
17651
17865
|
|
|
17652
17866
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
17653
17867
|
|