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/dist/s3db.iife.js CHANGED
@@ -3474,6 +3474,7 @@ ${JSON.stringify(validation, null, 2)}`
3474
3474
  this.attributes = attributes || {};
3475
3475
  this.passphrase = passphrase ?? "secret";
3476
3476
  this.options = lodashEs.merge({}, this.defaultOptions(), options);
3477
+ this.allNestedObjectsOptional = this.options.allNestedObjectsOptional ?? false;
3477
3478
  const processedAttributes = this.preprocessAttributesForValidation(this.attributes);
3478
3479
  this.validator = new ValidatorManager({ autoEncrypt: false }).compile(lodashEs.merge(
3479
3480
  { $$async: true },
@@ -3671,11 +3672,17 @@ ${JSON.stringify(validation, null, 2)}`
3671
3672
  const processed = {};
3672
3673
  for (const [key, value] of Object.entries(attributes)) {
3673
3674
  if (typeof value === "object" && value !== null && !Array.isArray(value)) {
3674
- processed[key] = {
3675
+ const isExplicitRequired = value.$$type && value.$$type.includes("required");
3676
+ const isExplicitOptional = value.$$type && value.$$type.includes("optional");
3677
+ const objectConfig = {
3675
3678
  type: "object",
3676
3679
  properties: this.preprocessAttributesForValidation(value),
3677
3680
  strict: false
3678
3681
  };
3682
+ if (isExplicitRequired) ; else if (isExplicitOptional || this.allNestedObjectsOptional) {
3683
+ objectConfig.optional = true;
3684
+ }
3685
+ processed[key] = objectConfig;
3679
3686
  } else {
3680
3687
  processed[key] = value;
3681
3688
  }
@@ -8837,6 +8844,7 @@ ${JSON.stringify(validation, null, 2)}`
8837
8844
  partitions: {},
8838
8845
  paranoid: true,
8839
8846
  // Security flag for dangerous operations
8847
+ allNestedObjectsOptional: options.allNestedObjectsOptional ?? false,
8840
8848
  ...options
8841
8849
  };
8842
8850
  this.hooks = {
@@ -8871,7 +8879,10 @@ ${JSON.stringify(validation, null, 2)}`
8871
8879
  attributes: this.attributes,
8872
8880
  passphrase,
8873
8881
  version: this.version,
8874
- options: this.options
8882
+ options: {
8883
+ ...this.options,
8884
+ allNestedObjectsOptional: this.options.allNestedObjectsOptional ?? false
8885
+ }
8875
8886
  });
8876
8887
  this.validatePartitions();
8877
8888
  this.setupPartitionHooks();
@@ -9798,7 +9809,7 @@ ${JSON.stringify(validation, null, 2)}`
9798
9809
  this.version = "1";
9799
9810
  this.s3dbVersion = (() => {
9800
9811
  try {
9801
- return true ? "4.1.1" : "latest";
9812
+ return true ? "4.1.3" : "latest";
9802
9813
  } catch (e) {
9803
9814
  return "latest";
9804
9815
  }
@@ -9910,16 +9921,21 @@ ${JSON.stringify(validation, null, 2)}`
9910
9921
  /**
9911
9922
  * Generate a consistent hash for a resource definition
9912
9923
  * @param {Object} definition - Resource definition to hash
9924
+ * @param {string} behavior - Resource behavior
9913
9925
  * @returns {string} SHA256 hash
9914
9926
  */
9915
- generateDefinitionHash(definition) {
9927
+ generateDefinitionHash(definition, behavior = void 0) {
9916
9928
  const attributes = definition.attributes;
9917
9929
  const stableAttributes = { ...attributes };
9918
9930
  if (definition.options?.timestamps) {
9919
9931
  delete stableAttributes.createdAt;
9920
9932
  delete stableAttributes.updatedAt;
9921
9933
  }
9922
- const stableString = jsonStableStringify(stableAttributes);
9934
+ const hashObj = {
9935
+ attributes: stableAttributes,
9936
+ behavior: behavior || definition.behavior || "user-management"
9937
+ };
9938
+ const stableString = jsonStableStringify(hashObj);
9923
9939
  return `sha256:${crypto.createHash("sha256").update(stableString).digest("hex")}`;
9924
9940
  }
9925
9941
  /**
@@ -10006,6 +10022,73 @@ ${JSON.stringify(validation, null, 2)}`
10006
10022
  resources: {}
10007
10023
  };
10008
10024
  }
10025
+ /**
10026
+ * Check if a resource exists by name
10027
+ * @param {string} name - Resource name
10028
+ * @returns {boolean} True if resource exists, false otherwise
10029
+ */
10030
+ resourceExists(name) {
10031
+ return !!this.resources[name];
10032
+ }
10033
+ /**
10034
+ * Check if a resource exists with the same definition hash
10035
+ * @param {string} name - Resource name
10036
+ * @param {Object} attributes - Resource attributes
10037
+ * @param {Object} options - Resource options
10038
+ * @param {string} behavior - Resource behavior
10039
+ * @returns {Object} Object with exists flag and hash information
10040
+ */
10041
+ resourceExistsWithSameHash({ name, attributes, options = {}, behavior = "user-management" }) {
10042
+ if (!this.resources[name]) {
10043
+ return { exists: false, sameHash: false, hash: null };
10044
+ }
10045
+ const tempResource = new Resource({
10046
+ name,
10047
+ attributes,
10048
+ behavior,
10049
+ observers: [],
10050
+ client: this.client,
10051
+ version: "temp",
10052
+ options: {
10053
+ cache: this.cache,
10054
+ ...options
10055
+ }
10056
+ });
10057
+ const newHash = this.generateDefinitionHash(tempResource.export(), behavior);
10058
+ const existingHash = this.generateDefinitionHash(this.resources[name].export(), this.resources[name].behavior);
10059
+ return {
10060
+ exists: true,
10061
+ sameHash: newHash === existingHash,
10062
+ hash: newHash,
10063
+ existingHash
10064
+ };
10065
+ }
10066
+ /**
10067
+ * Create a resource only if it doesn't exist with the same definition hash
10068
+ * @param {Object} params - Resource parameters
10069
+ * @param {string} params.name - Resource name
10070
+ * @param {Object} params.attributes - Resource attributes
10071
+ * @param {Object} params.options - Resource options
10072
+ * @param {string} params.behavior - Resource behavior
10073
+ * @returns {Object} Object with resource and created flag
10074
+ */
10075
+ async createResourceIfNotExists({ name, attributes, options = {}, behavior = "user-management" }) {
10076
+ const alreadyExists = !!this.resources[name];
10077
+ const hashCheck = this.resourceExistsWithSameHash({ name, attributes, options, behavior });
10078
+ if (hashCheck.exists && hashCheck.sameHash) {
10079
+ return {
10080
+ resource: this.resources[name],
10081
+ created: false,
10082
+ reason: "Resource already exists with same definition hash"
10083
+ };
10084
+ }
10085
+ const resource = await this.createResource({ name, attributes, options, behavior });
10086
+ return {
10087
+ resource,
10088
+ created: !alreadyExists,
10089
+ reason: alreadyExists ? "Resource updated with new definition" : "New resource created"
10090
+ };
10091
+ }
10009
10092
  async createResource({ name, attributes, options = {}, behavior = "user-management" }) {
10010
10093
  if (this.resources[name]) {
10011
10094
  const existingResource = this.resources[name];
@@ -10017,7 +10100,13 @@ ${JSON.stringify(validation, null, 2)}`
10017
10100
  existingResource.behavior = behavior;
10018
10101
  }
10019
10102
  existingResource.updateAttributes(attributes);
10020
- await this.uploadMetadataFile();
10103
+ const newHash = this.generateDefinitionHash(existingResource.export(), existingResource.behavior);
10104
+ const existingMetadata2 = this.savedMetadata?.resources?.[name];
10105
+ const currentVersion = existingMetadata2?.currentVersion || "v0";
10106
+ const existingVersionData = existingMetadata2?.versions?.[currentVersion];
10107
+ if (!existingVersionData || existingVersionData.hash !== newHash) {
10108
+ await this.uploadMetadataFile();
10109
+ }
10021
10110
  this.emit("s3db.resourceUpdated", name);
10022
10111
  return existingResource;
10023
10112
  }