s3db.js 4.1.2 → 4.1.3

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 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
@@ -9806,7 +9806,7 @@ class Database extends EventEmitter {
9806
9806
  this.version = "1";
9807
9807
  this.s3dbVersion = (() => {
9808
9808
  try {
9809
- return true ? "4.1.1" : "latest";
9809
+ return true ? "4.1.2" : "latest";
9810
9810
  } catch (e) {
9811
9811
  return "latest";
9812
9812
  }
@@ -9918,16 +9918,21 @@ class Database extends EventEmitter {
9918
9918
  /**
9919
9919
  * Generate a consistent hash for a resource definition
9920
9920
  * @param {Object} definition - Resource definition to hash
9921
+ * @param {string} behavior - Resource behavior
9921
9922
  * @returns {string} SHA256 hash
9922
9923
  */
9923
- generateDefinitionHash(definition) {
9924
+ generateDefinitionHash(definition, behavior = void 0) {
9924
9925
  const attributes = definition.attributes;
9925
9926
  const stableAttributes = { ...attributes };
9926
9927
  if (definition.options?.timestamps) {
9927
9928
  delete stableAttributes.createdAt;
9928
9929
  delete stableAttributes.updatedAt;
9929
9930
  }
9930
- const stableString = jsonStableStringify(stableAttributes);
9931
+ const hashObj = {
9932
+ attributes: stableAttributes,
9933
+ behavior: behavior || definition.behavior || "user-management"
9934
+ };
9935
+ const stableString = jsonStableStringify(hashObj);
9931
9936
  return `sha256:${crypto.createHash("sha256").update(stableString).digest("hex")}`;
9932
9937
  }
9933
9938
  /**
@@ -10014,6 +10019,73 @@ class Database extends EventEmitter {
10014
10019
  resources: {}
10015
10020
  };
10016
10021
  }
10022
+ /**
10023
+ * Check if a resource exists by name
10024
+ * @param {string} name - Resource name
10025
+ * @returns {boolean} True if resource exists, false otherwise
10026
+ */
10027
+ resourceExists(name) {
10028
+ return !!this.resources[name];
10029
+ }
10030
+ /**
10031
+ * Check if a resource exists with the same definition hash
10032
+ * @param {string} name - Resource name
10033
+ * @param {Object} attributes - Resource attributes
10034
+ * @param {Object} options - Resource options
10035
+ * @param {string} behavior - Resource behavior
10036
+ * @returns {Object} Object with exists flag and hash information
10037
+ */
10038
+ resourceExistsWithSameHash({ name, attributes, options = {}, behavior = "user-management" }) {
10039
+ if (!this.resources[name]) {
10040
+ return { exists: false, sameHash: false, hash: null };
10041
+ }
10042
+ const tempResource = new Resource({
10043
+ name,
10044
+ attributes,
10045
+ behavior,
10046
+ observers: [],
10047
+ client: this.client,
10048
+ version: "temp",
10049
+ options: {
10050
+ cache: this.cache,
10051
+ ...options
10052
+ }
10053
+ });
10054
+ const newHash = this.generateDefinitionHash(tempResource.export(), behavior);
10055
+ const existingHash = this.generateDefinitionHash(this.resources[name].export(), this.resources[name].behavior);
10056
+ return {
10057
+ exists: true,
10058
+ sameHash: newHash === existingHash,
10059
+ hash: newHash,
10060
+ existingHash
10061
+ };
10062
+ }
10063
+ /**
10064
+ * Create a resource only if it doesn't exist with the same definition hash
10065
+ * @param {Object} params - Resource parameters
10066
+ * @param {string} params.name - Resource name
10067
+ * @param {Object} params.attributes - Resource attributes
10068
+ * @param {Object} params.options - Resource options
10069
+ * @param {string} params.behavior - Resource behavior
10070
+ * @returns {Object} Object with resource and created flag
10071
+ */
10072
+ async createResourceIfNotExists({ name, attributes, options = {}, behavior = "user-management" }) {
10073
+ const alreadyExists = !!this.resources[name];
10074
+ const hashCheck = this.resourceExistsWithSameHash({ name, attributes, options, behavior });
10075
+ if (hashCheck.exists && hashCheck.sameHash) {
10076
+ return {
10077
+ resource: this.resources[name],
10078
+ created: false,
10079
+ reason: "Resource already exists with same definition hash"
10080
+ };
10081
+ }
10082
+ const resource = await this.createResource({ name, attributes, options, behavior });
10083
+ return {
10084
+ resource,
10085
+ created: !alreadyExists,
10086
+ reason: alreadyExists ? "Resource updated with new definition" : "New resource created"
10087
+ };
10088
+ }
10017
10089
  async createResource({ name, attributes, options = {}, behavior = "user-management" }) {
10018
10090
  if (this.resources[name]) {
10019
10091
  const existingResource = this.resources[name];
@@ -10025,7 +10097,13 @@ class Database extends EventEmitter {
10025
10097
  existingResource.behavior = behavior;
10026
10098
  }
10027
10099
  existingResource.updateAttributes(attributes);
10028
- await this.uploadMetadataFile();
10100
+ const newHash = this.generateDefinitionHash(existingResource.export(), existingResource.behavior);
10101
+ const existingMetadata2 = this.savedMetadata?.resources?.[name];
10102
+ const currentVersion = existingMetadata2?.currentVersion || "v0";
10103
+ const existingVersionData = existingMetadata2?.versions?.[currentVersion];
10104
+ if (!existingVersionData || existingVersionData.hash !== newHash) {
10105
+ await this.uploadMetadataFile();
10106
+ }
10029
10107
  this.emit("s3db.resourceUpdated", name);
10030
10108
  return existingResource;
10031
10109
  }