s3db.js 4.1.2 → 4.1.4
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/README.md +42 -0
- package/dist/s3db.cjs.js +95 -6
- package/dist/s3db.cjs.min.js +8 -8
- package/dist/s3db.es.js +95 -6
- package/dist/s3db.es.min.js +8 -8
- package/dist/s3db.iife.js +95 -6
- package/dist/s3db.iife.min.js +9 -9
- package/package.json +1 -1
package/dist/s3db.es.js
CHANGED
|
@@ -3480,6 +3480,7 @@ class Schema {
|
|
|
3480
3480
|
this.attributes = attributes || {};
|
|
3481
3481
|
this.passphrase = passphrase ?? "secret";
|
|
3482
3482
|
this.options = merge({}, this.defaultOptions(), options);
|
|
3483
|
+
this.allNestedObjectsOptional = this.options.allNestedObjectsOptional ?? false;
|
|
3483
3484
|
const processedAttributes = this.preprocessAttributesForValidation(this.attributes);
|
|
3484
3485
|
this.validator = new ValidatorManager({ autoEncrypt: false }).compile(merge(
|
|
3485
3486
|
{ $$async: true },
|
|
@@ -3677,11 +3678,17 @@ class Schema {
|
|
|
3677
3678
|
const processed = {};
|
|
3678
3679
|
for (const [key, value] of Object.entries(attributes)) {
|
|
3679
3680
|
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
3680
|
-
|
|
3681
|
+
const isExplicitRequired = value.$$type && value.$$type.includes("required");
|
|
3682
|
+
const isExplicitOptional = value.$$type && value.$$type.includes("optional");
|
|
3683
|
+
const objectConfig = {
|
|
3681
3684
|
type: "object",
|
|
3682
3685
|
properties: this.preprocessAttributesForValidation(value),
|
|
3683
3686
|
strict: false
|
|
3684
3687
|
};
|
|
3688
|
+
if (isExplicitRequired) ; else if (isExplicitOptional || this.allNestedObjectsOptional) {
|
|
3689
|
+
objectConfig.optional = true;
|
|
3690
|
+
}
|
|
3691
|
+
processed[key] = objectConfig;
|
|
3685
3692
|
} else {
|
|
3686
3693
|
processed[key] = value;
|
|
3687
3694
|
}
|
|
@@ -8843,6 +8850,7 @@ class Resource extends EventEmitter {
|
|
|
8843
8850
|
partitions: {},
|
|
8844
8851
|
paranoid: true,
|
|
8845
8852
|
// Security flag for dangerous operations
|
|
8853
|
+
allNestedObjectsOptional: options.allNestedObjectsOptional ?? false,
|
|
8846
8854
|
...options
|
|
8847
8855
|
};
|
|
8848
8856
|
this.hooks = {
|
|
@@ -8877,7 +8885,10 @@ class Resource extends EventEmitter {
|
|
|
8877
8885
|
attributes: this.attributes,
|
|
8878
8886
|
passphrase,
|
|
8879
8887
|
version: this.version,
|
|
8880
|
-
options:
|
|
8888
|
+
options: {
|
|
8889
|
+
...this.options,
|
|
8890
|
+
allNestedObjectsOptional: this.options.allNestedObjectsOptional ?? false
|
|
8891
|
+
}
|
|
8881
8892
|
});
|
|
8882
8893
|
this.validatePartitions();
|
|
8883
8894
|
this.setupPartitionHooks();
|
|
@@ -9804,7 +9815,7 @@ class Database extends EventEmitter {
|
|
|
9804
9815
|
this.version = "1";
|
|
9805
9816
|
this.s3dbVersion = (() => {
|
|
9806
9817
|
try {
|
|
9807
|
-
return true ? "4.1.
|
|
9818
|
+
return true ? "4.1.3" : "latest";
|
|
9808
9819
|
} catch (e) {
|
|
9809
9820
|
return "latest";
|
|
9810
9821
|
}
|
|
@@ -9916,16 +9927,21 @@ class Database extends EventEmitter {
|
|
|
9916
9927
|
/**
|
|
9917
9928
|
* Generate a consistent hash for a resource definition
|
|
9918
9929
|
* @param {Object} definition - Resource definition to hash
|
|
9930
|
+
* @param {string} behavior - Resource behavior
|
|
9919
9931
|
* @returns {string} SHA256 hash
|
|
9920
9932
|
*/
|
|
9921
|
-
generateDefinitionHash(definition) {
|
|
9933
|
+
generateDefinitionHash(definition, behavior = void 0) {
|
|
9922
9934
|
const attributes = definition.attributes;
|
|
9923
9935
|
const stableAttributes = { ...attributes };
|
|
9924
9936
|
if (definition.options?.timestamps) {
|
|
9925
9937
|
delete stableAttributes.createdAt;
|
|
9926
9938
|
delete stableAttributes.updatedAt;
|
|
9927
9939
|
}
|
|
9928
|
-
const
|
|
9940
|
+
const hashObj = {
|
|
9941
|
+
attributes: stableAttributes,
|
|
9942
|
+
behavior: behavior || definition.behavior || "user-management"
|
|
9943
|
+
};
|
|
9944
|
+
const stableString = jsonStableStringify(hashObj);
|
|
9929
9945
|
return `sha256:${createHash("sha256").update(stableString).digest("hex")}`;
|
|
9930
9946
|
}
|
|
9931
9947
|
/**
|
|
@@ -10012,6 +10028,73 @@ class Database extends EventEmitter {
|
|
|
10012
10028
|
resources: {}
|
|
10013
10029
|
};
|
|
10014
10030
|
}
|
|
10031
|
+
/**
|
|
10032
|
+
* Check if a resource exists by name
|
|
10033
|
+
* @param {string} name - Resource name
|
|
10034
|
+
* @returns {boolean} True if resource exists, false otherwise
|
|
10035
|
+
*/
|
|
10036
|
+
resourceExists(name) {
|
|
10037
|
+
return !!this.resources[name];
|
|
10038
|
+
}
|
|
10039
|
+
/**
|
|
10040
|
+
* Check if a resource exists with the same definition hash
|
|
10041
|
+
* @param {string} name - Resource name
|
|
10042
|
+
* @param {Object} attributes - Resource attributes
|
|
10043
|
+
* @param {Object} options - Resource options
|
|
10044
|
+
* @param {string} behavior - Resource behavior
|
|
10045
|
+
* @returns {Object} Object with exists flag and hash information
|
|
10046
|
+
*/
|
|
10047
|
+
resourceExistsWithSameHash({ name, attributes, options = {}, behavior = "user-management" }) {
|
|
10048
|
+
if (!this.resources[name]) {
|
|
10049
|
+
return { exists: false, sameHash: false, hash: null };
|
|
10050
|
+
}
|
|
10051
|
+
const tempResource = new Resource({
|
|
10052
|
+
name,
|
|
10053
|
+
attributes,
|
|
10054
|
+
behavior,
|
|
10055
|
+
observers: [],
|
|
10056
|
+
client: this.client,
|
|
10057
|
+
version: "temp",
|
|
10058
|
+
options: {
|
|
10059
|
+
cache: this.cache,
|
|
10060
|
+
...options
|
|
10061
|
+
}
|
|
10062
|
+
});
|
|
10063
|
+
const newHash = this.generateDefinitionHash(tempResource.export(), behavior);
|
|
10064
|
+
const existingHash = this.generateDefinitionHash(this.resources[name].export(), this.resources[name].behavior);
|
|
10065
|
+
return {
|
|
10066
|
+
exists: true,
|
|
10067
|
+
sameHash: newHash === existingHash,
|
|
10068
|
+
hash: newHash,
|
|
10069
|
+
existingHash
|
|
10070
|
+
};
|
|
10071
|
+
}
|
|
10072
|
+
/**
|
|
10073
|
+
* Create a resource only if it doesn't exist with the same definition hash
|
|
10074
|
+
* @param {Object} params - Resource parameters
|
|
10075
|
+
* @param {string} params.name - Resource name
|
|
10076
|
+
* @param {Object} params.attributes - Resource attributes
|
|
10077
|
+
* @param {Object} params.options - Resource options
|
|
10078
|
+
* @param {string} params.behavior - Resource behavior
|
|
10079
|
+
* @returns {Object} Object with resource and created flag
|
|
10080
|
+
*/
|
|
10081
|
+
async createResourceIfNotExists({ name, attributes, options = {}, behavior = "user-management" }) {
|
|
10082
|
+
const alreadyExists = !!this.resources[name];
|
|
10083
|
+
const hashCheck = this.resourceExistsWithSameHash({ name, attributes, options, behavior });
|
|
10084
|
+
if (hashCheck.exists && hashCheck.sameHash) {
|
|
10085
|
+
return {
|
|
10086
|
+
resource: this.resources[name],
|
|
10087
|
+
created: false,
|
|
10088
|
+
reason: "Resource already exists with same definition hash"
|
|
10089
|
+
};
|
|
10090
|
+
}
|
|
10091
|
+
const resource = await this.createResource({ name, attributes, options, behavior });
|
|
10092
|
+
return {
|
|
10093
|
+
resource,
|
|
10094
|
+
created: !alreadyExists,
|
|
10095
|
+
reason: alreadyExists ? "Resource updated with new definition" : "New resource created"
|
|
10096
|
+
};
|
|
10097
|
+
}
|
|
10015
10098
|
async createResource({ name, attributes, options = {}, behavior = "user-management" }) {
|
|
10016
10099
|
if (this.resources[name]) {
|
|
10017
10100
|
const existingResource = this.resources[name];
|
|
@@ -10023,7 +10106,13 @@ class Database extends EventEmitter {
|
|
|
10023
10106
|
existingResource.behavior = behavior;
|
|
10024
10107
|
}
|
|
10025
10108
|
existingResource.updateAttributes(attributes);
|
|
10026
|
-
|
|
10109
|
+
const newHash = this.generateDefinitionHash(existingResource.export(), existingResource.behavior);
|
|
10110
|
+
const existingMetadata2 = this.savedMetadata?.resources?.[name];
|
|
10111
|
+
const currentVersion = existingMetadata2?.currentVersion || "v0";
|
|
10112
|
+
const existingVersionData = existingMetadata2?.versions?.[currentVersion];
|
|
10113
|
+
if (!existingVersionData || existingVersionData.hash !== newHash) {
|
|
10114
|
+
await this.uploadMetadataFile();
|
|
10115
|
+
}
|
|
10027
10116
|
this.emit("s3db.resourceUpdated", name);
|
|
10028
10117
|
return existingResource;
|
|
10029
10118
|
}
|