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.cjs.js CHANGED
@@ -938,7 +938,7 @@ class Client extends EventEmitter {
938
938
  Key: this.config.keyPrefix ? path.join(this.config.keyPrefix, key) : key
939
939
  };
940
940
  try {
941
- const response = await this.client.send(new clientS3.HeadObjectCommand(options2));
941
+ const response = await this.sendCommand(new clientS3.HeadObjectCommand(options2));
942
942
  this.emit("headObject", response, options2);
943
943
  return response;
944
944
  } catch (error) {
@@ -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)) {
@@ -3565,6 +3566,7 @@ class Schema {
3565
3566
  version,
3566
3567
  attributes
3567
3568
  } = lodashEs.isString(data) ? JSON.parse(data) : data;
3569
+ attributes = Schema._importAttributes(attributes);
3568
3570
  const schema = new Schema({
3569
3571
  map,
3570
3572
  name,
@@ -3574,22 +3576,60 @@ class Schema {
3574
3576
  });
3575
3577
  return schema;
3576
3578
  }
3579
+ /**
3580
+ * Recursively import attributes, parsing only stringified objects (legacy)
3581
+ */
3582
+ static _importAttributes(attrs) {
3583
+ if (typeof attrs === "string") {
3584
+ try {
3585
+ const parsed = JSON.parse(attrs);
3586
+ if (typeof parsed === "object" && parsed !== null) {
3587
+ return Schema._importAttributes(parsed);
3588
+ }
3589
+ } catch (e) {
3590
+ }
3591
+ return attrs;
3592
+ }
3593
+ if (Array.isArray(attrs)) {
3594
+ return attrs.map((a) => Schema._importAttributes(a));
3595
+ }
3596
+ if (typeof attrs === "object" && attrs !== null) {
3597
+ const out = {};
3598
+ for (const [k, v] of Object.entries(attrs)) {
3599
+ out[k] = Schema._importAttributes(v);
3600
+ }
3601
+ return out;
3602
+ }
3603
+ return attrs;
3604
+ }
3577
3605
  export() {
3578
3606
  const data = {
3579
3607
  version: this.version,
3580
3608
  name: this.name,
3581
3609
  options: this.options,
3582
- attributes: lodashEs.cloneDeep(this.attributes),
3610
+ attributes: this._exportAttributes(this.attributes),
3583
3611
  map: this.map
3584
3612
  };
3585
- for (const [name, definition] of Object.entries(this.attributes)) {
3586
- if (typeof definition !== "string") {
3587
- data.attributes[name] = JSON.stringify(definition);
3588
- } else {
3589
- data.attributes[name] = definition;
3613
+ return data;
3614
+ }
3615
+ /**
3616
+ * Recursively export attributes, keeping objects as objects and only serializing leaves as string
3617
+ */
3618
+ _exportAttributes(attrs) {
3619
+ if (typeof attrs === "string") {
3620
+ return attrs;
3621
+ }
3622
+ if (Array.isArray(attrs)) {
3623
+ return attrs.map((a) => this._exportAttributes(a));
3624
+ }
3625
+ if (typeof attrs === "object" && attrs !== null) {
3626
+ const out = {};
3627
+ for (const [k, v] of Object.entries(attrs)) {
3628
+ out[k] = this._exportAttributes(v);
3590
3629
  }
3630
+ return out;
3591
3631
  }
3592
- return data;
3632
+ return attrs;
3593
3633
  }
3594
3634
  async applyHooksActions(resourceItem, hook) {
3595
3635
  for (const [attribute, actions] of Object.entries(this.options.hooks[hook])) {
@@ -3630,6 +3670,26 @@ class Schema {
3630
3670
  await this.applyHooksActions(rest, "afterUnmap");
3631
3671
  return flat.unflatten(rest);
3632
3672
  }
3673
+ /**
3674
+ * Preprocess attributes to convert nested objects into validator-compatible format
3675
+ * @param {Object} attributes - Original attributes
3676
+ * @returns {Object} Processed attributes for validator
3677
+ */
3678
+ preprocessAttributesForValidation(attributes) {
3679
+ const processed = {};
3680
+ for (const [key, value] of Object.entries(attributes)) {
3681
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
3682
+ processed[key] = {
3683
+ type: "object",
3684
+ properties: this.preprocessAttributesForValidation(value),
3685
+ strict: false
3686
+ };
3687
+ } else {
3688
+ processed[key] = value;
3689
+ }
3690
+ }
3691
+ return processed;
3692
+ }
3633
3693
  }
3634
3694
 
3635
3695
  var global$1 = (typeof global !== "undefined" ? global :
@@ -8945,7 +9005,7 @@ class Resource extends EventEmitter {
8945
9005
  continue;
8946
9006
  }
8947
9007
  for (const fieldName of Object.keys(partitionDef.fields)) {
8948
- if (!currentAttributes.includes(fieldName)) {
9008
+ if (!this.fieldExistsInAttributes(fieldName)) {
8949
9009
  throw new Error(
8950
9010
  `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
9011
  );
@@ -8953,6 +9013,25 @@ class Resource extends EventEmitter {
8953
9013
  }
8954
9014
  }
8955
9015
  }
9016
+ /**
9017
+ * Check if a field (including nested fields) exists in the current attributes
9018
+ * @param {string} fieldName - Field name (can be nested like 'utm.source')
9019
+ * @returns {boolean} True if field exists
9020
+ */
9021
+ fieldExistsInAttributes(fieldName) {
9022
+ if (!fieldName.includes(".")) {
9023
+ return Object.keys(this.attributes || {}).includes(fieldName);
9024
+ }
9025
+ const keys = fieldName.split(".");
9026
+ let currentLevel = this.attributes || {};
9027
+ for (const key of keys) {
9028
+ if (!currentLevel || typeof currentLevel !== "object" || !(key in currentLevel)) {
9029
+ return false;
9030
+ }
9031
+ currentLevel = currentLevel[key];
9032
+ }
9033
+ return true;
9034
+ }
8956
9035
  /**
8957
9036
  * Apply a single partition rule to a field value
8958
9037
  * @param {*} value - The field value
@@ -9015,17 +9094,38 @@ class Resource extends EventEmitter {
9015
9094
  const partitionSegments = [];
9016
9095
  const sortedFields = Object.entries(partition.fields).sort(([a], [b]) => a.localeCompare(b));
9017
9096
  for (const [fieldName, rule] of sortedFields) {
9018
- const fieldValue = this.applyPartitionRule(data[fieldName], rule);
9019
- if (fieldValue === void 0 || fieldValue === null) {
9097
+ const fieldValue = this.getNestedFieldValue(data, fieldName);
9098
+ const transformedValue = this.applyPartitionRule(fieldValue, rule);
9099
+ if (transformedValue === void 0 || transformedValue === null) {
9020
9100
  return null;
9021
9101
  }
9022
- partitionSegments.push(`${fieldName}=${fieldValue}`);
9102
+ partitionSegments.push(`${fieldName}=${transformedValue}`);
9023
9103
  }
9024
9104
  if (partitionSegments.length === 0) {
9025
9105
  return null;
9026
9106
  }
9027
9107
  return join(`resource=${this.name}`, `partition=${partitionName}`, ...partitionSegments, `id=${id}`);
9028
9108
  }
9109
+ /**
9110
+ * Get nested field value from data object using dot notation
9111
+ * @param {Object} data - Data object
9112
+ * @param {string} fieldPath - Field path (e.g., "utm.source", "address.city")
9113
+ * @returns {*} Field value
9114
+ */
9115
+ getNestedFieldValue(data, fieldPath) {
9116
+ if (!fieldPath.includes(".")) {
9117
+ return data[fieldPath];
9118
+ }
9119
+ const keys = fieldPath.split(".");
9120
+ let value = data;
9121
+ for (const key of keys) {
9122
+ if (value === null || value === void 0 || typeof value !== "object") {
9123
+ return void 0;
9124
+ }
9125
+ value = value[key];
9126
+ }
9127
+ return value;
9128
+ }
9029
9129
  async insert({ id, ...attributes }) {
9030
9130
  if (this.options.timestamps) {
9031
9131
  attributes.createdAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -9507,7 +9607,19 @@ class Resource extends EventEmitter {
9507
9607
  */
9508
9608
  getDefinitionHash() {
9509
9609
  const exportedSchema = this.schema.export();
9510
- const stableString = jsonStableStringify(exportedSchema);
9610
+ const stableSchema = {
9611
+ ...exportedSchema,
9612
+ attributes: { ...exportedSchema.attributes }
9613
+ };
9614
+ 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
+ }
9621
+ }
9622
+ const stableString = jsonStableStringify(stableSchema);
9511
9623
  return `sha256:${crypto.createHash("sha256").update(stableString).digest("hex")}`;
9512
9624
  }
9513
9625
  /**
@@ -9701,7 +9813,7 @@ class Database extends EventEmitter {
9701
9813
  this.version = "1";
9702
9814
  this.s3dbVersion = (() => {
9703
9815
  try {
9704
- return true ? "4.0.1" : "latest";
9816
+ return true ? "4.1.0" : "latest";
9705
9817
  } catch (e) {
9706
9818
  return "latest";
9707
9819
  }