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/README.md
CHANGED
|
@@ -830,6 +830,48 @@ const resource = await s3db.createResource({
|
|
|
830
830
|
});
|
|
831
831
|
```
|
|
832
832
|
|
|
833
|
+
#### Check Resource Existence
|
|
834
|
+
|
|
835
|
+
```javascript
|
|
836
|
+
// Check if a resource exists by name
|
|
837
|
+
const exists = s3db.resourceExists("users");
|
|
838
|
+
console.log(exists); // true or false
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
#### Create Resource If Not Exists
|
|
842
|
+
|
|
843
|
+
```javascript
|
|
844
|
+
// Create a resource only if it doesn't exist with the same definition hash
|
|
845
|
+
const result = await s3db.createResourceIfNotExists({
|
|
846
|
+
name: "users",
|
|
847
|
+
attributes: {
|
|
848
|
+
name: "string|required",
|
|
849
|
+
email: "email|required"
|
|
850
|
+
},
|
|
851
|
+
options: { timestamps: true },
|
|
852
|
+
behavior: "user-management"
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
console.log(result);
|
|
856
|
+
// {
|
|
857
|
+
// resource: Resource,
|
|
858
|
+
// created: true, // or false if already existed
|
|
859
|
+
// reason: "New resource created" // or "Resource already exists with same definition hash"
|
|
860
|
+
// }
|
|
861
|
+
|
|
862
|
+
// If the resource already exists with the same hash, it returns the existing resource
|
|
863
|
+
const result2 = await s3db.createResourceIfNotExists({
|
|
864
|
+
name: "users",
|
|
865
|
+
attributes: {
|
|
866
|
+
name: "string|required",
|
|
867
|
+
email: "email|required"
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
console.log(result2.created); // false
|
|
872
|
+
console.log(result2.reason); // "Resource already exists with same definition hash"
|
|
873
|
+
```
|
|
874
|
+
|
|
833
875
|
#### Get Resource Reference
|
|
834
876
|
|
|
835
877
|
```javascript
|
package/dist/s3db.cjs.js
CHANGED
|
@@ -3482,6 +3482,7 @@ class Schema {
|
|
|
3482
3482
|
this.attributes = attributes || {};
|
|
3483
3483
|
this.passphrase = passphrase ?? "secret";
|
|
3484
3484
|
this.options = lodashEs.merge({}, this.defaultOptions(), options);
|
|
3485
|
+
this.allNestedObjectsOptional = this.options.allNestedObjectsOptional ?? false;
|
|
3485
3486
|
const processedAttributes = this.preprocessAttributesForValidation(this.attributes);
|
|
3486
3487
|
this.validator = new ValidatorManager({ autoEncrypt: false }).compile(lodashEs.merge(
|
|
3487
3488
|
{ $$async: true },
|
|
@@ -3679,11 +3680,17 @@ class Schema {
|
|
|
3679
3680
|
const processed = {};
|
|
3680
3681
|
for (const [key, value] of Object.entries(attributes)) {
|
|
3681
3682
|
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
3682
|
-
|
|
3683
|
+
const isExplicitRequired = value.$$type && value.$$type.includes("required");
|
|
3684
|
+
const isExplicitOptional = value.$$type && value.$$type.includes("optional");
|
|
3685
|
+
const objectConfig = {
|
|
3683
3686
|
type: "object",
|
|
3684
3687
|
properties: this.preprocessAttributesForValidation(value),
|
|
3685
3688
|
strict: false
|
|
3686
3689
|
};
|
|
3690
|
+
if (isExplicitRequired) ; else if (isExplicitOptional || this.allNestedObjectsOptional) {
|
|
3691
|
+
objectConfig.optional = true;
|
|
3692
|
+
}
|
|
3693
|
+
processed[key] = objectConfig;
|
|
3687
3694
|
} else {
|
|
3688
3695
|
processed[key] = value;
|
|
3689
3696
|
}
|
|
@@ -8845,6 +8852,7 @@ class Resource extends EventEmitter {
|
|
|
8845
8852
|
partitions: {},
|
|
8846
8853
|
paranoid: true,
|
|
8847
8854
|
// Security flag for dangerous operations
|
|
8855
|
+
allNestedObjectsOptional: options.allNestedObjectsOptional ?? false,
|
|
8848
8856
|
...options
|
|
8849
8857
|
};
|
|
8850
8858
|
this.hooks = {
|
|
@@ -8879,7 +8887,10 @@ class Resource extends EventEmitter {
|
|
|
8879
8887
|
attributes: this.attributes,
|
|
8880
8888
|
passphrase,
|
|
8881
8889
|
version: this.version,
|
|
8882
|
-
options:
|
|
8890
|
+
options: {
|
|
8891
|
+
...this.options,
|
|
8892
|
+
allNestedObjectsOptional: this.options.allNestedObjectsOptional ?? false
|
|
8893
|
+
}
|
|
8883
8894
|
});
|
|
8884
8895
|
this.validatePartitions();
|
|
8885
8896
|
this.setupPartitionHooks();
|
|
@@ -9806,7 +9817,7 @@ class Database extends EventEmitter {
|
|
|
9806
9817
|
this.version = "1";
|
|
9807
9818
|
this.s3dbVersion = (() => {
|
|
9808
9819
|
try {
|
|
9809
|
-
return true ? "4.1.
|
|
9820
|
+
return true ? "4.1.3" : "latest";
|
|
9810
9821
|
} catch (e) {
|
|
9811
9822
|
return "latest";
|
|
9812
9823
|
}
|
|
@@ -9918,16 +9929,21 @@ class Database extends EventEmitter {
|
|
|
9918
9929
|
/**
|
|
9919
9930
|
* Generate a consistent hash for a resource definition
|
|
9920
9931
|
* @param {Object} definition - Resource definition to hash
|
|
9932
|
+
* @param {string} behavior - Resource behavior
|
|
9921
9933
|
* @returns {string} SHA256 hash
|
|
9922
9934
|
*/
|
|
9923
|
-
generateDefinitionHash(definition) {
|
|
9935
|
+
generateDefinitionHash(definition, behavior = void 0) {
|
|
9924
9936
|
const attributes = definition.attributes;
|
|
9925
9937
|
const stableAttributes = { ...attributes };
|
|
9926
9938
|
if (definition.options?.timestamps) {
|
|
9927
9939
|
delete stableAttributes.createdAt;
|
|
9928
9940
|
delete stableAttributes.updatedAt;
|
|
9929
9941
|
}
|
|
9930
|
-
const
|
|
9942
|
+
const hashObj = {
|
|
9943
|
+
attributes: stableAttributes,
|
|
9944
|
+
behavior: behavior || definition.behavior || "user-management"
|
|
9945
|
+
};
|
|
9946
|
+
const stableString = jsonStableStringify(hashObj);
|
|
9931
9947
|
return `sha256:${crypto.createHash("sha256").update(stableString).digest("hex")}`;
|
|
9932
9948
|
}
|
|
9933
9949
|
/**
|
|
@@ -10014,6 +10030,73 @@ class Database extends EventEmitter {
|
|
|
10014
10030
|
resources: {}
|
|
10015
10031
|
};
|
|
10016
10032
|
}
|
|
10033
|
+
/**
|
|
10034
|
+
* Check if a resource exists by name
|
|
10035
|
+
* @param {string} name - Resource name
|
|
10036
|
+
* @returns {boolean} True if resource exists, false otherwise
|
|
10037
|
+
*/
|
|
10038
|
+
resourceExists(name) {
|
|
10039
|
+
return !!this.resources[name];
|
|
10040
|
+
}
|
|
10041
|
+
/**
|
|
10042
|
+
* Check if a resource exists with the same definition hash
|
|
10043
|
+
* @param {string} name - Resource name
|
|
10044
|
+
* @param {Object} attributes - Resource attributes
|
|
10045
|
+
* @param {Object} options - Resource options
|
|
10046
|
+
* @param {string} behavior - Resource behavior
|
|
10047
|
+
* @returns {Object} Object with exists flag and hash information
|
|
10048
|
+
*/
|
|
10049
|
+
resourceExistsWithSameHash({ name, attributes, options = {}, behavior = "user-management" }) {
|
|
10050
|
+
if (!this.resources[name]) {
|
|
10051
|
+
return { exists: false, sameHash: false, hash: null };
|
|
10052
|
+
}
|
|
10053
|
+
const tempResource = new Resource({
|
|
10054
|
+
name,
|
|
10055
|
+
attributes,
|
|
10056
|
+
behavior,
|
|
10057
|
+
observers: [],
|
|
10058
|
+
client: this.client,
|
|
10059
|
+
version: "temp",
|
|
10060
|
+
options: {
|
|
10061
|
+
cache: this.cache,
|
|
10062
|
+
...options
|
|
10063
|
+
}
|
|
10064
|
+
});
|
|
10065
|
+
const newHash = this.generateDefinitionHash(tempResource.export(), behavior);
|
|
10066
|
+
const existingHash = this.generateDefinitionHash(this.resources[name].export(), this.resources[name].behavior);
|
|
10067
|
+
return {
|
|
10068
|
+
exists: true,
|
|
10069
|
+
sameHash: newHash === existingHash,
|
|
10070
|
+
hash: newHash,
|
|
10071
|
+
existingHash
|
|
10072
|
+
};
|
|
10073
|
+
}
|
|
10074
|
+
/**
|
|
10075
|
+
* Create a resource only if it doesn't exist with the same definition hash
|
|
10076
|
+
* @param {Object} params - Resource parameters
|
|
10077
|
+
* @param {string} params.name - Resource name
|
|
10078
|
+
* @param {Object} params.attributes - Resource attributes
|
|
10079
|
+
* @param {Object} params.options - Resource options
|
|
10080
|
+
* @param {string} params.behavior - Resource behavior
|
|
10081
|
+
* @returns {Object} Object with resource and created flag
|
|
10082
|
+
*/
|
|
10083
|
+
async createResourceIfNotExists({ name, attributes, options = {}, behavior = "user-management" }) {
|
|
10084
|
+
const alreadyExists = !!this.resources[name];
|
|
10085
|
+
const hashCheck = this.resourceExistsWithSameHash({ name, attributes, options, behavior });
|
|
10086
|
+
if (hashCheck.exists && hashCheck.sameHash) {
|
|
10087
|
+
return {
|
|
10088
|
+
resource: this.resources[name],
|
|
10089
|
+
created: false,
|
|
10090
|
+
reason: "Resource already exists with same definition hash"
|
|
10091
|
+
};
|
|
10092
|
+
}
|
|
10093
|
+
const resource = await this.createResource({ name, attributes, options, behavior });
|
|
10094
|
+
return {
|
|
10095
|
+
resource,
|
|
10096
|
+
created: !alreadyExists,
|
|
10097
|
+
reason: alreadyExists ? "Resource updated with new definition" : "New resource created"
|
|
10098
|
+
};
|
|
10099
|
+
}
|
|
10017
10100
|
async createResource({ name, attributes, options = {}, behavior = "user-management" }) {
|
|
10018
10101
|
if (this.resources[name]) {
|
|
10019
10102
|
const existingResource = this.resources[name];
|
|
@@ -10025,7 +10108,13 @@ class Database extends EventEmitter {
|
|
|
10025
10108
|
existingResource.behavior = behavior;
|
|
10026
10109
|
}
|
|
10027
10110
|
existingResource.updateAttributes(attributes);
|
|
10028
|
-
|
|
10111
|
+
const newHash = this.generateDefinitionHash(existingResource.export(), existingResource.behavior);
|
|
10112
|
+
const existingMetadata2 = this.savedMetadata?.resources?.[name];
|
|
10113
|
+
const currentVersion = existingMetadata2?.currentVersion || "v0";
|
|
10114
|
+
const existingVersionData = existingMetadata2?.versions?.[currentVersion];
|
|
10115
|
+
if (!existingVersionData || existingVersionData.hash !== newHash) {
|
|
10116
|
+
await this.uploadMetadataFile();
|
|
10117
|
+
}
|
|
10029
10118
|
this.emit("s3db.resourceUpdated", name);
|
|
10030
10119
|
return existingResource;
|
|
10031
10120
|
}
|