s3db.js 4.0.2 → 4.1.1

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
@@ -930,7 +930,7 @@ ${JSON.stringify(validation, null, 2)}`
930
930
  Key: this.config.keyPrefix ? path.join(this.config.keyPrefix, key) : key
931
931
  };
932
932
  try {
933
- const response = await this.client.send(new clientS3.HeadObjectCommand(options2));
933
+ const response = await this.sendCommand(new clientS3.HeadObjectCommand(options2));
934
934
  this.emit("headObject", response, options2);
935
935
  return response;
936
936
  } catch (error) {
@@ -3474,9 +3474,10 @@ ${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
+ const processedAttributes = this.preprocessAttributesForValidation(this.attributes);
3477
3478
  this.validator = new ValidatorManager({ autoEncrypt: false }).compile(lodashEs.merge(
3478
3479
  { $$async: true },
3479
- lodashEs.cloneDeep(this.attributes)
3480
+ processedAttributes
3480
3481
  ));
3481
3482
  if (this.options.generateAutoHooks) this.generateAutoHooks();
3482
3483
  if (!lodashEs.isEmpty(map)) {
@@ -3557,6 +3558,7 @@ ${JSON.stringify(validation, null, 2)}`
3557
3558
  version,
3558
3559
  attributes
3559
3560
  } = lodashEs.isString(data) ? JSON.parse(data) : data;
3561
+ attributes = Schema._importAttributes(attributes);
3560
3562
  const schema = new Schema({
3561
3563
  map,
3562
3564
  name,
@@ -3566,22 +3568,60 @@ ${JSON.stringify(validation, null, 2)}`
3566
3568
  });
3567
3569
  return schema;
3568
3570
  }
3571
+ /**
3572
+ * Recursively import attributes, parsing only stringified objects (legacy)
3573
+ */
3574
+ static _importAttributes(attrs) {
3575
+ if (typeof attrs === "string") {
3576
+ try {
3577
+ const parsed = JSON.parse(attrs);
3578
+ if (typeof parsed === "object" && parsed !== null) {
3579
+ return Schema._importAttributes(parsed);
3580
+ }
3581
+ } catch (e) {
3582
+ }
3583
+ return attrs;
3584
+ }
3585
+ if (Array.isArray(attrs)) {
3586
+ return attrs.map((a) => Schema._importAttributes(a));
3587
+ }
3588
+ if (typeof attrs === "object" && attrs !== null) {
3589
+ const out = {};
3590
+ for (const [k, v] of Object.entries(attrs)) {
3591
+ out[k] = Schema._importAttributes(v);
3592
+ }
3593
+ return out;
3594
+ }
3595
+ return attrs;
3596
+ }
3569
3597
  export() {
3570
3598
  const data = {
3571
3599
  version: this.version,
3572
3600
  name: this.name,
3573
3601
  options: this.options,
3574
- attributes: lodashEs.cloneDeep(this.attributes),
3602
+ attributes: this._exportAttributes(this.attributes),
3575
3603
  map: this.map
3576
3604
  };
3577
- for (const [name, definition] of Object.entries(this.attributes)) {
3578
- if (typeof definition !== "string") {
3579
- data.attributes[name] = JSON.stringify(definition);
3580
- } else {
3581
- data.attributes[name] = definition;
3605
+ return data;
3606
+ }
3607
+ /**
3608
+ * Recursively export attributes, keeping objects as objects and only serializing leaves as string
3609
+ */
3610
+ _exportAttributes(attrs) {
3611
+ if (typeof attrs === "string") {
3612
+ return attrs;
3613
+ }
3614
+ if (Array.isArray(attrs)) {
3615
+ return attrs.map((a) => this._exportAttributes(a));
3616
+ }
3617
+ if (typeof attrs === "object" && attrs !== null) {
3618
+ const out = {};
3619
+ for (const [k, v] of Object.entries(attrs)) {
3620
+ out[k] = this._exportAttributes(v);
3582
3621
  }
3622
+ return out;
3583
3623
  }
3584
- return data;
3624
+ return attrs;
3585
3625
  }
3586
3626
  async applyHooksActions(resourceItem, hook) {
3587
3627
  for (const [attribute, actions] of Object.entries(this.options.hooks[hook])) {
@@ -3622,6 +3662,26 @@ ${JSON.stringify(validation, null, 2)}`
3622
3662
  await this.applyHooksActions(rest, "afterUnmap");
3623
3663
  return flat.unflatten(rest);
3624
3664
  }
3665
+ /**
3666
+ * Preprocess attributes to convert nested objects into validator-compatible format
3667
+ * @param {Object} attributes - Original attributes
3668
+ * @returns {Object} Processed attributes for validator
3669
+ */
3670
+ preprocessAttributesForValidation(attributes) {
3671
+ const processed = {};
3672
+ for (const [key, value] of Object.entries(attributes)) {
3673
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
3674
+ processed[key] = {
3675
+ type: "object",
3676
+ properties: this.preprocessAttributesForValidation(value),
3677
+ strict: false
3678
+ };
3679
+ } else {
3680
+ processed[key] = value;
3681
+ }
3682
+ }
3683
+ return processed;
3684
+ }
3625
3685
  }
3626
3686
 
3627
3687
  var global$1 = (typeof global !== "undefined" ? global :
@@ -8937,7 +8997,7 @@ ${JSON.stringify(validation, null, 2)}`
8937
8997
  continue;
8938
8998
  }
8939
8999
  for (const fieldName of Object.keys(partitionDef.fields)) {
8940
- if (!currentAttributes.includes(fieldName)) {
9000
+ if (!this.fieldExistsInAttributes(fieldName)) {
8941
9001
  throw new Error(
8942
9002
  `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.`
8943
9003
  );
@@ -8945,6 +9005,25 @@ ${JSON.stringify(validation, null, 2)}`
8945
9005
  }
8946
9006
  }
8947
9007
  }
9008
+ /**
9009
+ * Check if a field (including nested fields) exists in the current attributes
9010
+ * @param {string} fieldName - Field name (can be nested like 'utm.source')
9011
+ * @returns {boolean} True if field exists
9012
+ */
9013
+ fieldExistsInAttributes(fieldName) {
9014
+ if (!fieldName.includes(".")) {
9015
+ return Object.keys(this.attributes || {}).includes(fieldName);
9016
+ }
9017
+ const keys = fieldName.split(".");
9018
+ let currentLevel = this.attributes || {};
9019
+ for (const key of keys) {
9020
+ if (!currentLevel || typeof currentLevel !== "object" || !(key in currentLevel)) {
9021
+ return false;
9022
+ }
9023
+ currentLevel = currentLevel[key];
9024
+ }
9025
+ return true;
9026
+ }
8948
9027
  /**
8949
9028
  * Apply a single partition rule to a field value
8950
9029
  * @param {*} value - The field value
@@ -9007,17 +9086,38 @@ ${JSON.stringify(validation, null, 2)}`
9007
9086
  const partitionSegments = [];
9008
9087
  const sortedFields = Object.entries(partition.fields).sort(([a], [b]) => a.localeCompare(b));
9009
9088
  for (const [fieldName, rule] of sortedFields) {
9010
- const fieldValue = this.applyPartitionRule(data[fieldName], rule);
9011
- if (fieldValue === void 0 || fieldValue === null) {
9089
+ const fieldValue = this.getNestedFieldValue(data, fieldName);
9090
+ const transformedValue = this.applyPartitionRule(fieldValue, rule);
9091
+ if (transformedValue === void 0 || transformedValue === null) {
9012
9092
  return null;
9013
9093
  }
9014
- partitionSegments.push(`${fieldName}=${fieldValue}`);
9094
+ partitionSegments.push(`${fieldName}=${transformedValue}`);
9015
9095
  }
9016
9096
  if (partitionSegments.length === 0) {
9017
9097
  return null;
9018
9098
  }
9019
9099
  return join(`resource=${this.name}`, `partition=${partitionName}`, ...partitionSegments, `id=${id}`);
9020
9100
  }
9101
+ /**
9102
+ * Get nested field value from data object using dot notation
9103
+ * @param {Object} data - Data object
9104
+ * @param {string} fieldPath - Field path (e.g., "utm.source", "address.city")
9105
+ * @returns {*} Field value
9106
+ */
9107
+ getNestedFieldValue(data, fieldPath) {
9108
+ if (!fieldPath.includes(".")) {
9109
+ return data[fieldPath];
9110
+ }
9111
+ const keys = fieldPath.split(".");
9112
+ let value = data;
9113
+ for (const key of keys) {
9114
+ if (value === null || value === void 0 || typeof value !== "object") {
9115
+ return void 0;
9116
+ }
9117
+ value = value[key];
9118
+ }
9119
+ return value;
9120
+ }
9021
9121
  async insert({ id, ...attributes }) {
9022
9122
  if (this.options.timestamps) {
9023
9123
  attributes.createdAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -9499,7 +9599,19 @@ ${JSON.stringify(validation, null, 2)}`
9499
9599
  */
9500
9600
  getDefinitionHash() {
9501
9601
  const exportedSchema = this.schema.export();
9502
- const stableString = jsonStableStringify(exportedSchema);
9602
+ const stableSchema = {
9603
+ ...exportedSchema,
9604
+ attributes: { ...exportedSchema.attributes }
9605
+ };
9606
+ if (this.options.timestamps) {
9607
+ delete stableSchema.attributes.createdAt;
9608
+ delete stableSchema.attributes.updatedAt;
9609
+ if (stableSchema.options && stableSchema.options.partitions) {
9610
+ delete stableSchema.options.partitions.byCreatedDate;
9611
+ delete stableSchema.options.partitions.byUpdatedDate;
9612
+ }
9613
+ }
9614
+ const stableString = jsonStableStringify(stableSchema);
9503
9615
  return `sha256:${crypto.createHash("sha256").update(stableString).digest("hex")}`;
9504
9616
  }
9505
9617
  /**
@@ -9693,7 +9805,7 @@ ${JSON.stringify(validation, null, 2)}`
9693
9805
  this.version = "1";
9694
9806
  this.s3dbVersion = (() => {
9695
9807
  try {
9696
- return true ? "4.0.1" : "latest";
9808
+ return true ? "4.1.0" : "latest";
9697
9809
  } catch (e) {
9698
9810
  return "latest";
9699
9811
  }