s3db.js 4.0.1 → 4.1.0

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.cjs.js CHANGED
@@ -3482,9 +3482,10 @@ class Schema {
3482
3482
  this.attributes = attributes || {};
3483
3483
  this.passphrase = passphrase ?? "secret";
3484
3484
  this.options = lodashEs.merge({}, this.defaultOptions(), options);
3485
+ const processedAttributes = this.preprocessAttributesForValidation(this.attributes);
3485
3486
  this.validator = new ValidatorManager({ autoEncrypt: false }).compile(lodashEs.merge(
3486
3487
  { $$async: true },
3487
- lodashEs.cloneDeep(this.attributes)
3488
+ processedAttributes
3488
3489
  ));
3489
3490
  if (this.options.generateAutoHooks) this.generateAutoHooks();
3490
3491
  if (!lodashEs.isEmpty(map)) {
@@ -3630,6 +3631,26 @@ class Schema {
3630
3631
  await this.applyHooksActions(rest, "afterUnmap");
3631
3632
  return flat.unflatten(rest);
3632
3633
  }
3634
+ /**
3635
+ * Preprocess attributes to convert nested objects into validator-compatible format
3636
+ * @param {Object} attributes - Original attributes
3637
+ * @returns {Object} Processed attributes for validator
3638
+ */
3639
+ preprocessAttributesForValidation(attributes) {
3640
+ const processed = {};
3641
+ for (const [key, value] of Object.entries(attributes)) {
3642
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
3643
+ processed[key] = {
3644
+ type: "object",
3645
+ properties: this.preprocessAttributesForValidation(value),
3646
+ strict: false
3647
+ };
3648
+ } else {
3649
+ processed[key] = value;
3650
+ }
3651
+ }
3652
+ return processed;
3653
+ }
3633
3654
  }
3634
3655
 
3635
3656
  var global$1 = (typeof global !== "undefined" ? global :
@@ -8945,7 +8966,7 @@ class Resource extends EventEmitter {
8945
8966
  continue;
8946
8967
  }
8947
8968
  for (const fieldName of Object.keys(partitionDef.fields)) {
8948
- if (!currentAttributes.includes(fieldName)) {
8969
+ if (!this.fieldExistsInAttributes(fieldName)) {
8949
8970
  throw new Error(
8950
8971
  `Partition '${partitionName}' uses field '${fieldName}' which does not exist in resource version '${this.version}'. Available fields: ${currentAttributes.join(", ")}. This version of resource does not have support for this partition.`
8951
8972
  );
@@ -8953,6 +8974,25 @@ class Resource extends EventEmitter {
8953
8974
  }
8954
8975
  }
8955
8976
  }
8977
+ /**
8978
+ * Check if a field (including nested fields) exists in the current attributes
8979
+ * @param {string} fieldName - Field name (can be nested like 'utm.source')
8980
+ * @returns {boolean} True if field exists
8981
+ */
8982
+ fieldExistsInAttributes(fieldName) {
8983
+ if (!fieldName.includes(".")) {
8984
+ return Object.keys(this.attributes || {}).includes(fieldName);
8985
+ }
8986
+ const keys = fieldName.split(".");
8987
+ let currentLevel = this.attributes || {};
8988
+ for (const key of keys) {
8989
+ if (!currentLevel || typeof currentLevel !== "object" || !(key in currentLevel)) {
8990
+ return false;
8991
+ }
8992
+ currentLevel = currentLevel[key];
8993
+ }
8994
+ return true;
8995
+ }
8956
8996
  /**
8957
8997
  * Apply a single partition rule to a field value
8958
8998
  * @param {*} value - The field value
@@ -9015,17 +9055,38 @@ class Resource extends EventEmitter {
9015
9055
  const partitionSegments = [];
9016
9056
  const sortedFields = Object.entries(partition.fields).sort(([a], [b]) => a.localeCompare(b));
9017
9057
  for (const [fieldName, rule] of sortedFields) {
9018
- const fieldValue = this.applyPartitionRule(data[fieldName], rule);
9019
- if (fieldValue === void 0 || fieldValue === null) {
9058
+ const fieldValue = this.getNestedFieldValue(data, fieldName);
9059
+ const transformedValue = this.applyPartitionRule(fieldValue, rule);
9060
+ if (transformedValue === void 0 || transformedValue === null) {
9020
9061
  return null;
9021
9062
  }
9022
- partitionSegments.push(`${fieldName}=${fieldValue}`);
9063
+ partitionSegments.push(`${fieldName}=${transformedValue}`);
9023
9064
  }
9024
9065
  if (partitionSegments.length === 0) {
9025
9066
  return null;
9026
9067
  }
9027
9068
  return join(`resource=${this.name}`, `partition=${partitionName}`, ...partitionSegments, `id=${id}`);
9028
9069
  }
9070
+ /**
9071
+ * Get nested field value from data object using dot notation
9072
+ * @param {Object} data - Data object
9073
+ * @param {string} fieldPath - Field path (e.g., "utm.source", "address.city")
9074
+ * @returns {*} Field value
9075
+ */
9076
+ getNestedFieldValue(data, fieldPath) {
9077
+ if (!fieldPath.includes(".")) {
9078
+ return data[fieldPath];
9079
+ }
9080
+ const keys = fieldPath.split(".");
9081
+ let value = data;
9082
+ for (const key of keys) {
9083
+ if (value === null || value === void 0 || typeof value !== "object") {
9084
+ return void 0;
9085
+ }
9086
+ value = value[key];
9087
+ }
9088
+ return value;
9089
+ }
9029
9090
  async insert({ id, ...attributes }) {
9030
9091
  if (this.options.timestamps) {
9031
9092
  attributes.createdAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -9701,7 +9762,7 @@ class Database extends EventEmitter {
9701
9762
  this.version = "1";
9702
9763
  this.s3dbVersion = (() => {
9703
9764
  try {
9704
- return true ? "4.0.1" : "latest";
9765
+ return true ? "4.0.2" : "latest";
9705
9766
  } catch (e) {
9706
9767
  return "latest";
9707
9768
  }