s3db.js 4.1.1 → 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
@@ -9606,20 +9606,13 @@ class Resource extends EventEmitter {
9606
9606
  * @returns {string} SHA256 hash of the schema definition
9607
9607
  */
9608
9608
  getDefinitionHash() {
9609
- const exportedSchema = this.schema.export();
9610
- const stableSchema = {
9611
- ...exportedSchema,
9612
- attributes: { ...exportedSchema.attributes }
9613
- };
9609
+ const attributes = this.schema.export().attributes;
9610
+ const stableAttributes = { ...attributes };
9614
9611
  if (this.options.timestamps) {
9615
- delete stableSchema.attributes.createdAt;
9616
- delete stableSchema.attributes.updatedAt;
9617
- if (stableSchema.options && stableSchema.options.partitions) {
9618
- delete stableSchema.options.partitions.byCreatedDate;
9619
- delete stableSchema.options.partitions.byUpdatedDate;
9620
- }
9612
+ delete stableAttributes.createdAt;
9613
+ delete stableAttributes.updatedAt;
9621
9614
  }
9622
- const stableString = jsonStableStringify(stableSchema);
9615
+ const stableString = jsonStableStringify(stableAttributes);
9623
9616
  return `sha256:${crypto.createHash("sha256").update(stableString).digest("hex")}`;
9624
9617
  }
9625
9618
  /**
@@ -9813,7 +9806,7 @@ class Database extends EventEmitter {
9813
9806
  this.version = "1";
9814
9807
  this.s3dbVersion = (() => {
9815
9808
  try {
9816
- return true ? "4.1.0" : "latest";
9809
+ return true ? "4.1.2" : "latest";
9817
9810
  } catch (e) {
9818
9811
  return "latest";
9819
9812
  }
@@ -9925,10 +9918,21 @@ class Database extends EventEmitter {
9925
9918
  /**
9926
9919
  * Generate a consistent hash for a resource definition
9927
9920
  * @param {Object} definition - Resource definition to hash
9921
+ * @param {string} behavior - Resource behavior
9928
9922
  * @returns {string} SHA256 hash
9929
9923
  */
9930
- generateDefinitionHash(definition) {
9931
- const stableString = jsonStableStringify(definition);
9924
+ generateDefinitionHash(definition, behavior = void 0) {
9925
+ const attributes = definition.attributes;
9926
+ const stableAttributes = { ...attributes };
9927
+ if (definition.options?.timestamps) {
9928
+ delete stableAttributes.createdAt;
9929
+ delete stableAttributes.updatedAt;
9930
+ }
9931
+ const hashObj = {
9932
+ attributes: stableAttributes,
9933
+ behavior: behavior || definition.behavior || "user-management"
9934
+ };
9935
+ const stableString = jsonStableStringify(hashObj);
9932
9936
  return `sha256:${crypto.createHash("sha256").update(stableString).digest("hex")}`;
9933
9937
  }
9934
9938
  /**
@@ -10015,6 +10019,73 @@ class Database extends EventEmitter {
10015
10019
  resources: {}
10016
10020
  };
10017
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
+ }
10018
10089
  async createResource({ name, attributes, options = {}, behavior = "user-management" }) {
10019
10090
  if (this.resources[name]) {
10020
10091
  const existingResource = this.resources[name];
@@ -10026,7 +10097,13 @@ class Database extends EventEmitter {
10026
10097
  existingResource.behavior = behavior;
10027
10098
  }
10028
10099
  existingResource.updateAttributes(attributes);
10029
- 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
+ }
10030
10107
  this.emit("s3db.resourceUpdated", name);
10031
10108
  return existingResource;
10032
10109
  }