s3db.js 4.1.14 → 5.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.iife.js CHANGED
@@ -237,7 +237,7 @@ var S3DB = (function (exports, nanoid, lodashEs, promisePool, clientS3, crypto,
237
237
 
238
238
  const idGenerator = nanoid.customAlphabet(nanoid.urlAlphabet, 22);
239
239
  const passwordAlphabet = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789";
240
- nanoid.customAlphabet(passwordAlphabet, 12);
240
+ const passwordGenerator = nanoid.customAlphabet(passwordAlphabet, 12);
241
241
 
242
242
  var domain;
243
243
 
@@ -3382,7 +3382,7 @@ ${JSON.stringify(validation, null, 2)}`, {
3382
3382
  var jsonStableStringifyExports = requireJsonStableStringify();
3383
3383
  var jsonStableStringify = /*@__PURE__*/getDefaultExportFromCjs(jsonStableStringifyExports);
3384
3384
 
3385
- async function custom(actual, errors, schema) {
3385
+ async function secretHandler(actual, errors, schema) {
3386
3386
  if (!this.passphrase) {
3387
3387
  errors.push({ actual, type: "encryptionKeyMissing" });
3388
3388
  return actual;
@@ -3395,6 +3395,10 @@ ${JSON.stringify(validation, null, 2)}`, {
3395
3395
  }
3396
3396
  return actual;
3397
3397
  }
3398
+ async function jsonHandler(actual, errors, schema) {
3399
+ if (lodashEs.isString(actual)) return actual;
3400
+ return JSON.stringify(actual);
3401
+ }
3398
3402
  class Validator extends FastestValidator {
3399
3403
  constructor({ options, passphrase, autoEncrypt = true } = {}) {
3400
3404
  super(lodashEs.merge({}, {
@@ -3416,7 +3420,7 @@ ${JSON.stringify(validation, null, 2)}`, {
3416
3420
  this.autoEncrypt = autoEncrypt;
3417
3421
  this.alias("secret", {
3418
3422
  type: "string",
3419
- custom: this.autoEncrypt ? custom : void 0,
3423
+ custom: this.autoEncrypt ? secretHandler : void 0,
3420
3424
  messages: {
3421
3425
  string: "The '{field}' field must be a string.",
3422
3426
  stringMin: "This secret '{field}' field length must be at least {expected} long."
@@ -3424,11 +3428,15 @@ ${JSON.stringify(validation, null, 2)}`, {
3424
3428
  });
3425
3429
  this.alias("secretAny", {
3426
3430
  type: "any",
3427
- custom: this.autoEncrypt ? custom : void 0
3431
+ custom: this.autoEncrypt ? secretHandler : void 0
3428
3432
  });
3429
3433
  this.alias("secretNumber", {
3430
3434
  type: "number",
3431
- custom: this.autoEncrypt ? custom : void 0
3435
+ custom: this.autoEncrypt ? secretHandler : void 0
3436
+ });
3437
+ this.alias("json", {
3438
+ type: "any",
3439
+ custom: this.autoEncrypt ? jsonHandler : void 0
3432
3440
  });
3433
3441
  }
3434
3442
  }
@@ -3440,10 +3448,31 @@ ${JSON.stringify(validation, null, 2)}`, {
3440
3448
  }
3441
3449
  });
3442
3450
 
3451
+ function toBase36(num) {
3452
+ return num.toString(36);
3453
+ }
3454
+ function generateBase36Mapping(keys) {
3455
+ const mapping = {};
3456
+ const reversedMapping = {};
3457
+ keys.forEach((key, index) => {
3458
+ const base36Key = toBase36(index);
3459
+ mapping[key] = base36Key;
3460
+ reversedMapping[base36Key] = key;
3461
+ });
3462
+ return { mapping, reversedMapping };
3463
+ }
3443
3464
  const SchemaActions = {
3444
3465
  trim: (value) => value.trim(),
3445
3466
  encrypt: (value, { passphrase }) => encrypt(value, passphrase),
3446
- decrypt: (value, { passphrase }) => decrypt(value, passphrase),
3467
+ decrypt: async (value, { passphrase }) => {
3468
+ try {
3469
+ const raw = await decrypt(value, passphrase);
3470
+ return raw;
3471
+ } catch (error) {
3472
+ console.warn(`Schema decrypt error: ${error}`, error);
3473
+ return value;
3474
+ }
3475
+ },
3447
3476
  toString: (value) => String(value),
3448
3477
  fromArray: (value, { separator }) => {
3449
3478
  if (value === null || value === void 0 || !Array.isArray(value)) {
@@ -3534,8 +3563,9 @@ ${JSON.stringify(validation, null, 2)}`, {
3534
3563
  const leafKeys = Object.keys(flatAttrs).filter((k) => !k.includes("$$"));
3535
3564
  const objectKeys = this.extractObjectKeys(this.attributes);
3536
3565
  const allKeys = [.../* @__PURE__ */ new Set([...leafKeys, ...objectKeys])];
3537
- this.reversedMap = { ...allKeys };
3538
- this.map = lodashEs.invert(this.reversedMap);
3566
+ const { mapping, reversedMapping } = generateBase36Mapping(allKeys);
3567
+ this.map = mapping;
3568
+ this.reversedMap = reversedMapping;
3539
3569
  }
3540
3570
  }
3541
3571
  defaultOptions() {
@@ -3576,24 +3606,27 @@ ${JSON.stringify(validation, null, 2)}`, {
3576
3606
  if (definition.includes("array")) {
3577
3607
  this.addHook("beforeMap", name, "fromArray");
3578
3608
  this.addHook("afterUnmap", name, "toArray");
3579
- } else {
3580
- if (definition.includes("secret")) {
3581
- if (this.options.autoEncrypt) {
3582
- this.addHook("beforeMap", name, "encrypt");
3583
- }
3584
- if (this.options.autoDecrypt) {
3585
- this.addHook("afterUnmap", name, "decrypt");
3586
- }
3587
- }
3588
- if (definition.includes("number")) {
3589
- this.addHook("beforeMap", name, "toString");
3590
- this.addHook("afterUnmap", name, "toNumber");
3609
+ }
3610
+ if (definition.includes("secret")) {
3611
+ if (this.options.autoEncrypt) {
3612
+ this.addHook("beforeMap", name, "encrypt");
3591
3613
  }
3592
- if (definition.includes("boolean")) {
3593
- this.addHook("beforeMap", name, "fromBool");
3594
- this.addHook("afterUnmap", name, "toBool");
3614
+ if (this.options.autoDecrypt) {
3615
+ this.addHook("afterUnmap", name, "decrypt");
3595
3616
  }
3596
3617
  }
3618
+ if (definition.includes("number")) {
3619
+ this.addHook("beforeMap", name, "toString");
3620
+ this.addHook("afterUnmap", name, "toNumber");
3621
+ }
3622
+ if (definition.includes("boolean")) {
3623
+ this.addHook("beforeMap", name, "fromBool");
3624
+ this.addHook("afterUnmap", name, "toBool");
3625
+ }
3626
+ if (definition.includes("json")) {
3627
+ this.addHook("beforeMap", name, "toJSON");
3628
+ this.addHook("afterUnmap", name, "fromJSON");
3629
+ }
3597
3630
  }
3598
3631
  }
3599
3632
  static import(data) {
@@ -8608,7 +8641,6 @@ ${JSON.stringify(validation, null, 2)}`, {
8608
8641
  function calculateAttributeNamesSize(mappedObject) {
8609
8642
  let totalSize = 0;
8610
8643
  for (const key of Object.keys(mappedObject)) {
8611
- if (key === "_v") continue;
8612
8644
  totalSize += calculateUTF8Bytes(key);
8613
8645
  }
8614
8646
  return totalSize;
@@ -8652,6 +8684,30 @@ ${JSON.stringify(validation, null, 2)}`, {
8652
8684
  const namesSize = calculateAttributeNamesSize(mappedObject);
8653
8685
  return valueTotal + namesSize;
8654
8686
  }
8687
+ function getSizeBreakdown(mappedObject) {
8688
+ const valueSizes = calculateAttributeSizes(mappedObject);
8689
+ const namesSize = calculateAttributeNamesSize(mappedObject);
8690
+ const valueTotal = Object.values(valueSizes).reduce((sum, size) => sum + size, 0);
8691
+ const total = valueTotal + namesSize;
8692
+ const sortedAttributes = Object.entries(valueSizes).sort(([, a], [, b]) => b - a).map(([key, size]) => ({
8693
+ attribute: key,
8694
+ size,
8695
+ percentage: (size / total * 100).toFixed(2) + "%"
8696
+ }));
8697
+ return {
8698
+ total,
8699
+ valueSizes,
8700
+ namesSize,
8701
+ valueTotal,
8702
+ breakdown: sortedAttributes,
8703
+ // Add detailed breakdown including names
8704
+ detailedBreakdown: {
8705
+ values: valueTotal,
8706
+ names: namesSize,
8707
+ total
8708
+ }
8709
+ };
8710
+ }
8655
8711
 
8656
8712
  const S3_METADATA_LIMIT_BYTES = 2048;
8657
8713
  async function handleInsert$3({ resource, data, mappedData }) {
@@ -8872,6 +8928,7 @@ ${JSON.stringify(validation, null, 2)}`, {
8872
8928
  }
8873
8929
  return behavior;
8874
8930
  }
8931
+ const AVAILABLE_BEHAVIORS = Object.keys(behaviors);
8875
8932
  const DEFAULT_BEHAVIOR = "user-management";
8876
8933
 
8877
8934
  class Resource extends EventEmitter {
@@ -8941,17 +8998,8 @@ ${validation.errors.join("\n")}`);
8941
8998
  partitions = {},
8942
8999
  paranoid = true,
8943
9000
  allNestedObjectsOptional = true,
8944
- hooks = {},
8945
- options = {}
9001
+ hooks = {}
8946
9002
  } = config;
8947
- const mergedOptions = {
8948
- cache: typeof options.cache === "boolean" ? options.cache : cache,
8949
- autoDecrypt: typeof options.autoDecrypt === "boolean" ? options.autoDecrypt : autoDecrypt,
8950
- timestamps: typeof options.timestamps === "boolean" ? options.timestamps : timestamps,
8951
- paranoid: typeof options.paranoid === "boolean" ? options.paranoid : paranoid,
8952
- allNestedObjectsOptional: typeof options.allNestedObjectsOptional === "boolean" ? options.allNestedObjectsOptional : allNestedObjectsOptional,
8953
- partitions: options.partitions || partitions || {}
8954
- };
8955
9003
  this.name = name;
8956
9004
  this.client = client;
8957
9005
  this.version = version;
@@ -8960,13 +9008,13 @@ ${validation.errors.join("\n")}`);
8960
9008
  this.parallelism = parallelism;
8961
9009
  this.passphrase = passphrase ?? "secret";
8962
9010
  this.config = {
8963
- cache: mergedOptions.cache,
9011
+ cache,
8964
9012
  hooks,
8965
- paranoid: mergedOptions.paranoid,
8966
- timestamps: mergedOptions.timestamps,
8967
- partitions: mergedOptions.partitions,
8968
- autoDecrypt: mergedOptions.autoDecrypt,
8969
- allNestedObjectsOptional: mergedOptions.allNestedObjectsOptional
9013
+ paranoid,
9014
+ timestamps,
9015
+ partitions,
9016
+ autoDecrypt,
9017
+ allNestedObjectsOptional
8970
9018
  };
8971
9019
  this.hooks = {
8972
9020
  preInsert: [],
@@ -8977,39 +9025,7 @@ ${validation.errors.join("\n")}`);
8977
9025
  afterDelete: []
8978
9026
  };
8979
9027
  this.attributes = attributes || {};
8980
- if (this.config.timestamps) {
8981
- this.attributes.createdAt = "string|optional";
8982
- this.attributes.updatedAt = "string|optional";
8983
- if (!this.config.partitions) {
8984
- this.config.partitions = {};
8985
- }
8986
- if (!this.config.partitions.byCreatedDate) {
8987
- this.config.partitions.byCreatedDate = {
8988
- fields: {
8989
- createdAt: "date|maxlength:10"
8990
- }
8991
- };
8992
- }
8993
- if (!this.config.partitions.byUpdatedDate) {
8994
- this.config.partitions.byUpdatedDate = {
8995
- fields: {
8996
- updatedAt: "date|maxlength:10"
8997
- }
8998
- };
8999
- }
9000
- }
9001
- this.schema = new Schema({
9002
- name,
9003
- attributes: this.attributes,
9004
- passphrase,
9005
- version: this.version,
9006
- options: {
9007
- autoDecrypt: this.config.autoDecrypt,
9008
- allNestedObjectsOptional: this.config.allNestedObjectsOptional
9009
- }
9010
- });
9011
- this.setupPartitionHooks();
9012
- this.validatePartitions();
9028
+ this.applyConfiguration();
9013
9029
  if (hooks) {
9014
9030
  for (const [event, hooksArr] of Object.entries(hooks)) {
9015
9031
  if (Array.isArray(hooksArr) && this.hooks[event]) {
@@ -9048,15 +9064,17 @@ ${validation.errors.join("\n")}`);
9048
9064
  return exported;
9049
9065
  }
9050
9066
  /**
9051
- * Update resource attributes and rebuild schema
9052
- * @param {Object} newAttributes - New attributes definition
9067
+ * Apply configuration settings (timestamps, partitions, hooks)
9068
+ * This method ensures that all configuration-dependent features are properly set up
9053
9069
  */
9054
- updateAttributes(newAttributes) {
9055
- const oldAttributes = this.attributes;
9056
- this.attributes = newAttributes;
9070
+ applyConfiguration() {
9057
9071
  if (this.config.timestamps) {
9058
- newAttributes.createdAt = "string|optional";
9059
- newAttributes.updatedAt = "string|optional";
9072
+ if (!this.attributes.createdAt) {
9073
+ this.attributes.createdAt = "string|optional";
9074
+ }
9075
+ if (!this.attributes.updatedAt) {
9076
+ this.attributes.updatedAt = "string|optional";
9077
+ }
9060
9078
  if (!this.config.partitions) {
9061
9079
  this.config.partitions = {};
9062
9080
  }
@@ -9075,9 +9093,10 @@ ${validation.errors.join("\n")}`);
9075
9093
  };
9076
9094
  }
9077
9095
  }
9096
+ this.setupPartitionHooks();
9078
9097
  this.schema = new Schema({
9079
9098
  name: this.name,
9080
- attributes: newAttributes,
9099
+ attributes: this.attributes,
9081
9100
  passphrase: this.passphrase,
9082
9101
  version: this.version,
9083
9102
  options: {
@@ -9085,8 +9104,16 @@ ${validation.errors.join("\n")}`);
9085
9104
  allNestedObjectsOptional: this.config.allNestedObjectsOptional
9086
9105
  }
9087
9106
  });
9088
- this.setupPartitionHooks();
9089
9107
  this.validatePartitions();
9108
+ }
9109
+ /**
9110
+ * Update resource attributes and rebuild schema
9111
+ * @param {Object} newAttributes - New attributes definition
9112
+ */
9113
+ updateAttributes(newAttributes) {
9114
+ const oldAttributes = this.attributes;
9115
+ this.attributes = newAttributes;
9116
+ this.applyConfiguration();
9090
9117
  return { oldAttributes, newAttributes };
9091
9118
  }
9092
9119
  /**
@@ -9174,7 +9201,7 @@ ${validation.errors.join("\n")}`);
9174
9201
  for (const fieldName of Object.keys(partitionDef.fields)) {
9175
9202
  if (!this.fieldExistsInAttributes(fieldName)) {
9176
9203
  throw new Error(
9177
- `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.`
9204
+ `Partition '${partitionName}' uses field '${fieldName}' which does not exist in resource attributes. Available fields: ${currentAttributes.join(", ")}.`
9178
9205
  );
9179
9206
  }
9180
9207
  }
@@ -9287,7 +9314,11 @@ ${validation.errors.join("\n")}`);
9287
9314
  if (partitionSegments.length === 0) {
9288
9315
  return null;
9289
9316
  }
9290
- return join(`resource=${this.name}`, `partition=${partitionName}`, ...partitionSegments, `id=${id}`);
9317
+ const finalId = id || data?.id;
9318
+ if (!finalId) {
9319
+ return null;
9320
+ }
9321
+ return join(`resource=${this.name}`, `partition=${partitionName}`, ...partitionSegments, `id=${finalId}`);
9291
9322
  }
9292
9323
  /**
9293
9324
  * Get nested field value from data object using dot notation
@@ -9516,12 +9547,12 @@ ${validation.errors.join("\n")}`);
9516
9547
  * console.log(updatedUser.updatedAt); // ISO timestamp
9517
9548
  */
9518
9549
  async update(id, attributes) {
9519
- const live = await this.get(id);
9550
+ const originalData = await this.get(id);
9520
9551
  if (this.config.timestamps) {
9521
9552
  attributes.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
9522
9553
  }
9523
9554
  const preProcessedData = await this.executeHooks("preUpdate", attributes);
9524
- const attrs = lodashEs.merge(live, preProcessedData);
9555
+ const attrs = lodashEs.merge(originalData, preProcessedData);
9525
9556
  delete attrs.id;
9526
9557
  const { isValid, errors, data: validated } = await this.validate(attrs);
9527
9558
  if (!isValid) {
@@ -9532,6 +9563,9 @@ ${validation.errors.join("\n")}`);
9532
9563
  validation: errors
9533
9564
  });
9534
9565
  }
9566
+ const oldData = { ...originalData, id };
9567
+ const newData = { ...validated, id };
9568
+ await this.handlePartitionReferenceUpdates(oldData, newData);
9535
9569
  const mappedData = await this.schema.mapper(validated);
9536
9570
  const behaviorImpl = getBehavior(this.behavior);
9537
9571
  const { mappedData: processedMetadata, body } = await behaviorImpl.handleUpdate({
@@ -9567,7 +9601,6 @@ ${validation.errors.join("\n")}`);
9567
9601
  });
9568
9602
  validated.id = id;
9569
9603
  await this.executeHooks("afterUpdate", validated);
9570
- await this.updatePartitionReferences(validated);
9571
9604
  this.emit("update", preProcessedData, validated);
9572
9605
  return validated;
9573
9606
  }
@@ -9854,23 +9887,104 @@ ${validation.errors.join("\n")}`);
9854
9887
  * });
9855
9888
  */
9856
9889
  async list({ partition = null, partitionValues = {}, limit, offset = 0 } = {}) {
9857
- if (!partition) {
9858
- const ids2 = await this.listIds({ partition, partitionValues });
9859
- let filteredIds2 = ids2.slice(offset);
9890
+ try {
9891
+ if (!partition) {
9892
+ let ids2 = [];
9893
+ try {
9894
+ ids2 = await this.listIds({ partition, partitionValues });
9895
+ } catch (listIdsError) {
9896
+ console.warn(`Failed to get list IDs:`, listIdsError.message);
9897
+ return [];
9898
+ }
9899
+ let filteredIds2 = ids2.slice(offset);
9900
+ if (limit) {
9901
+ filteredIds2 = filteredIds2.slice(0, limit);
9902
+ }
9903
+ const { results: results2, errors: errors2 } = await promisePool.PromisePool.for(filteredIds2).withConcurrency(this.parallelism).handleError(async (error, id) => {
9904
+ console.warn(`Failed to get resource ${id}:`, error.message);
9905
+ return null;
9906
+ }).process(async (id) => {
9907
+ try {
9908
+ return await this.get(id);
9909
+ } catch (error) {
9910
+ if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
9911
+ console.warn(`Decryption failed for ${id}, returning basic info`);
9912
+ return {
9913
+ id,
9914
+ _decryptionFailed: true,
9915
+ _error: error.message
9916
+ };
9917
+ }
9918
+ throw error;
9919
+ }
9920
+ });
9921
+ const validResults2 = results2.filter((item) => item !== null);
9922
+ this.emit("list", { partition, partitionValues, count: validResults2.length, errors: errors2.length });
9923
+ return validResults2;
9924
+ }
9925
+ if (!this.config.partitions || !this.config.partitions[partition]) {
9926
+ console.warn(`Partition '${partition}' not found in resource '${this.name}'`);
9927
+ this.emit("list", { partition, partitionValues, count: 0, errors: 0 });
9928
+ return [];
9929
+ }
9930
+ const partitionDef = this.config.partitions[partition];
9931
+ const partitionSegments = [];
9932
+ const sortedFields = Object.entries(partitionDef.fields).sort(([a], [b]) => a.localeCompare(b));
9933
+ for (const [fieldName, rule] of sortedFields) {
9934
+ const value = partitionValues[fieldName];
9935
+ if (value !== void 0 && value !== null) {
9936
+ const transformedValue = this.applyPartitionRule(value, rule);
9937
+ partitionSegments.push(`${fieldName}=${transformedValue}`);
9938
+ }
9939
+ }
9940
+ let prefix;
9941
+ if (partitionSegments.length > 0) {
9942
+ prefix = `resource=${this.name}/partition=${partition}/${partitionSegments.join("/")}`;
9943
+ } else {
9944
+ prefix = `resource=${this.name}/partition=${partition}`;
9945
+ }
9946
+ let keys = [];
9947
+ try {
9948
+ keys = await this.client.getAllKeys({ prefix });
9949
+ } catch (getKeysError) {
9950
+ console.warn(`Failed to get partition keys:`, getKeysError.message);
9951
+ return [];
9952
+ }
9953
+ const ids = keys.map((key) => {
9954
+ const parts = key.split("/");
9955
+ const idPart = parts.find((part) => part.startsWith("id="));
9956
+ return idPart ? idPart.replace("id=", "") : null;
9957
+ }).filter(Boolean);
9958
+ let filteredIds = ids.slice(offset);
9860
9959
  if (limit) {
9861
- filteredIds2 = filteredIds2.slice(0, limit);
9960
+ filteredIds = filteredIds.slice(0, limit);
9862
9961
  }
9863
- const { results: results2, errors: errors2 } = await promisePool.PromisePool.for(filteredIds2).withConcurrency(this.parallelism).handleError(async (error, id) => {
9864
- console.warn(`Failed to get resource ${id}:`, error.message);
9962
+ const { results, errors } = await promisePool.PromisePool.for(filteredIds).withConcurrency(this.parallelism).handleError(async (error, id) => {
9963
+ console.warn(`Failed to get partition resource ${id}:`, error.message);
9865
9964
  return null;
9866
9965
  }).process(async (id) => {
9867
9966
  try {
9868
- return await this.get(id);
9967
+ const keyForId = keys.find((key) => key.includes(`id=${id}`));
9968
+ if (!keyForId) {
9969
+ throw new Error(`Partition key not found for ID ${id}`);
9970
+ }
9971
+ const keyParts = keyForId.split("/");
9972
+ const actualPartitionValues = {};
9973
+ for (const [fieldName, rule] of sortedFields) {
9974
+ const fieldPart = keyParts.find((part) => part.startsWith(`${fieldName}=`));
9975
+ if (fieldPart) {
9976
+ const value = fieldPart.replace(`${fieldName}=`, "");
9977
+ actualPartitionValues[fieldName] = value;
9978
+ }
9979
+ }
9980
+ return await this.getFromPartition({ id, partitionName: partition, partitionValues: actualPartitionValues });
9869
9981
  } catch (error) {
9870
9982
  if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
9871
- console.warn(`Decryption failed for ${id}, returning basic info`);
9983
+ console.warn(`Decryption failed for partition resource ${id}, returning basic info`);
9872
9984
  return {
9873
9985
  id,
9986
+ _partition: partition,
9987
+ _partitionValues: partitionValues,
9874
9988
  _decryptionFailed: true,
9875
9989
  _error: error.message
9876
9990
  };
@@ -9878,62 +9992,19 @@ ${validation.errors.join("\n")}`);
9878
9992
  throw error;
9879
9993
  }
9880
9994
  });
9881
- const validResults2 = results2.filter((item) => item !== null);
9882
- this.emit("list", { partition, partitionValues, count: validResults2.length, errors: errors2.length });
9883
- return validResults2;
9884
- }
9885
- if (!this.config.partitions || !this.config.partitions[partition]) {
9886
- throw new Error(`Partition '${partition}' not found`);
9887
- }
9888
- const partitionDef = this.config.partitions[partition];
9889
- const partitionSegments = [];
9890
- const sortedFields = Object.entries(partitionDef.fields).sort(([a], [b]) => a.localeCompare(b));
9891
- for (const [fieldName, rule] of sortedFields) {
9892
- const value = partitionValues[fieldName];
9893
- if (value !== void 0 && value !== null) {
9894
- const transformedValue = this.applyPartitionRule(value, rule);
9895
- partitionSegments.push(`${fieldName}=${transformedValue}`);
9995
+ const validResults = results.filter((item) => item !== null);
9996
+ this.emit("list", { partition, partitionValues, count: validResults.length, errors: errors.length });
9997
+ return validResults;
9998
+ } catch (error) {
9999
+ if (error.message.includes("Partition '") && error.message.includes("' not found")) {
10000
+ console.warn(`Partition error in list method:`, error.message);
10001
+ this.emit("list", { partition, partitionValues, count: 0, errors: 1 });
10002
+ return [];
9896
10003
  }
10004
+ console.error(`Critical error in list method:`, error.message);
10005
+ this.emit("list", { partition, partitionValues, count: 0, errors: 1 });
10006
+ return [];
9897
10007
  }
9898
- let prefix;
9899
- if (partitionSegments.length > 0) {
9900
- prefix = `resource=${this.name}/partition=${partition}/${partitionSegments.join("/")}`;
9901
- } else {
9902
- prefix = `resource=${this.name}/partition=${partition}`;
9903
- }
9904
- const keys = await this.client.getAllKeys({ prefix });
9905
- const ids = keys.map((key) => {
9906
- const parts = key.split("/");
9907
- const idPart = parts.find((part) => part.startsWith("id="));
9908
- return idPart ? idPart.replace("id=", "") : null;
9909
- }).filter(Boolean);
9910
- let filteredIds = ids.slice(offset);
9911
- if (limit) {
9912
- filteredIds = filteredIds.slice(0, limit);
9913
- }
9914
- const { results, errors } = await promisePool.PromisePool.for(filteredIds).withConcurrency(this.parallelism).handleError(async (error, id) => {
9915
- console.warn(`Failed to get partition resource ${id}:`, error.message);
9916
- return null;
9917
- }).process(async (id) => {
9918
- try {
9919
- return await this.getFromPartition({ id, partitionName: partition, partitionValues });
9920
- } catch (error) {
9921
- if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
9922
- console.warn(`Decryption failed for partition resource ${id}, returning basic info`);
9923
- return {
9924
- id,
9925
- _partition: partition,
9926
- _partitionValues: partitionValues,
9927
- _decryptionFailed: true,
9928
- _error: error.message
9929
- };
9930
- }
9931
- throw error;
9932
- }
9933
- });
9934
- const validResults = results.filter((item) => item !== null);
9935
- this.emit("list", { partition, partitionValues, count: validResults.length, errors: errors.length });
9936
- return validResults;
9937
10008
  }
9938
10009
  /**
9939
10010
  * Get multiple resources by their IDs
@@ -10040,36 +10111,67 @@ ${validation.errors.join("\n")}`);
10040
10111
  * console.log(`Got ${fastPage.items.length} items`); // totalItems will be null
10041
10112
  */
10042
10113
  async page({ offset = 0, size = 100, partition = null, partitionValues = {}, skipCount = false } = {}) {
10043
- let totalItems = null;
10044
- let totalPages = null;
10045
- if (!skipCount) {
10046
- totalItems = await this.count({ partition, partitionValues });
10047
- totalPages = Math.ceil(totalItems / size);
10048
- }
10049
- const page = Math.floor(offset / size);
10050
- const items = await this.list({
10051
- partition,
10052
- partitionValues,
10053
- limit: size,
10054
- offset
10055
- });
10056
- const result = {
10057
- items,
10058
- totalItems,
10059
- page,
10060
- pageSize: size,
10061
- totalPages,
10062
- // Add additional metadata for debugging
10063
- _debug: {
10064
- requestedSize: size,
10065
- requestedOffset: offset,
10066
- actualItemsReturned: items.length,
10067
- skipCount,
10068
- hasTotalItems: totalItems !== null
10114
+ try {
10115
+ let totalItems = null;
10116
+ let totalPages = null;
10117
+ if (!skipCount) {
10118
+ try {
10119
+ totalItems = await this.count({ partition, partitionValues });
10120
+ totalPages = Math.ceil(totalItems / size);
10121
+ } catch (countError) {
10122
+ console.warn(`Failed to get count for page:`, countError.message);
10123
+ totalItems = null;
10124
+ totalPages = null;
10125
+ }
10069
10126
  }
10070
- };
10071
- this.emit("page", result);
10072
- return result;
10127
+ const page = Math.floor(offset / size);
10128
+ let items = [];
10129
+ try {
10130
+ items = await this.list({
10131
+ partition,
10132
+ partitionValues,
10133
+ limit: size,
10134
+ offset
10135
+ });
10136
+ } catch (listError) {
10137
+ console.warn(`Failed to get items for page:`, listError.message);
10138
+ items = [];
10139
+ }
10140
+ const result = {
10141
+ items,
10142
+ totalItems,
10143
+ page,
10144
+ pageSize: size,
10145
+ totalPages,
10146
+ // Add additional metadata for debugging
10147
+ _debug: {
10148
+ requestedSize: size,
10149
+ requestedOffset: offset,
10150
+ actualItemsReturned: items.length,
10151
+ skipCount,
10152
+ hasTotalItems: totalItems !== null
10153
+ }
10154
+ };
10155
+ this.emit("page", result);
10156
+ return result;
10157
+ } catch (error) {
10158
+ console.error(`Critical error in page method:`, error.message);
10159
+ return {
10160
+ items: [],
10161
+ totalItems: null,
10162
+ page: Math.floor(offset / size),
10163
+ pageSize: size,
10164
+ totalPages: null,
10165
+ _debug: {
10166
+ requestedSize: size,
10167
+ requestedOffset: offset,
10168
+ actualItemsReturned: 0,
10169
+ skipCount,
10170
+ hasTotalItems: false,
10171
+ error: error.message
10172
+ }
10173
+ };
10174
+ }
10073
10175
  }
10074
10176
  readable() {
10075
10177
  const stream = new ResourceReader({ resource: this });
@@ -10362,7 +10464,118 @@ ${validation.errors.join("\n")}`);
10362
10464
  return results.slice(0, limit);
10363
10465
  }
10364
10466
  /**
10365
- * Update partition objects to keep them in sync
10467
+ * Handle partition reference updates with change detection
10468
+ * @param {Object} oldData - Original object data before update
10469
+ * @param {Object} newData - Updated object data
10470
+ */
10471
+ async handlePartitionReferenceUpdates(oldData, newData) {
10472
+ const partitions = this.config.partitions;
10473
+ if (!partitions || Object.keys(partitions).length === 0) {
10474
+ return;
10475
+ }
10476
+ for (const [partitionName, partition] of Object.entries(partitions)) {
10477
+ try {
10478
+ await this.handlePartitionReferenceUpdate(partitionName, partition, oldData, newData);
10479
+ } catch (error) {
10480
+ console.warn(`Failed to update partition references for ${partitionName}:`, error.message);
10481
+ }
10482
+ }
10483
+ const id = newData.id || oldData.id;
10484
+ for (const [partitionName, partition] of Object.entries(partitions)) {
10485
+ const prefix = `resource=${this.name}/partition=${partitionName}`;
10486
+ let allKeys = [];
10487
+ try {
10488
+ allKeys = await this.client.getAllKeys({ prefix });
10489
+ } catch (error) {
10490
+ console.warn(`Aggressive cleanup: could not list keys for partition ${partitionName}:`, error.message);
10491
+ continue;
10492
+ }
10493
+ const validKey = this.getPartitionKey({ partitionName, id, data: newData });
10494
+ for (const key of allKeys) {
10495
+ if (key.endsWith(`/id=${id}`) && key !== validKey) {
10496
+ try {
10497
+ await this.client.deleteObject(key);
10498
+ } catch (error) {
10499
+ console.warn(`Aggressive cleanup: could not delete stale partition key ${key}:`, error.message);
10500
+ }
10501
+ }
10502
+ }
10503
+ }
10504
+ }
10505
+ /**
10506
+ * Handle partition reference update for a specific partition
10507
+ * @param {string} partitionName - Name of the partition
10508
+ * @param {Object} partition - Partition definition
10509
+ * @param {Object} oldData - Original object data before update
10510
+ * @param {Object} newData - Updated object data
10511
+ */
10512
+ async handlePartitionReferenceUpdate(partitionName, partition, oldData, newData) {
10513
+ const id = newData.id || oldData.id;
10514
+ const oldPartitionKey = this.getPartitionKey({ partitionName, id, data: oldData });
10515
+ const newPartitionKey = this.getPartitionKey({ partitionName, id, data: newData });
10516
+ if (oldPartitionKey !== newPartitionKey) {
10517
+ if (oldPartitionKey) {
10518
+ try {
10519
+ await this.client.deleteObject(oldPartitionKey);
10520
+ } catch (error) {
10521
+ console.warn(`Old partition object could not be deleted for ${partitionName}:`, error.message);
10522
+ }
10523
+ }
10524
+ if (newPartitionKey) {
10525
+ try {
10526
+ const mappedData = await this.schema.mapper(newData);
10527
+ if (mappedData.undefined !== void 0) delete mappedData.undefined;
10528
+ const behaviorImpl = getBehavior(this.behavior);
10529
+ const { mappedData: processedMetadata, body } = await behaviorImpl.handleUpdate({
10530
+ resource: this,
10531
+ id,
10532
+ data: newData,
10533
+ mappedData
10534
+ });
10535
+ if (processedMetadata.undefined !== void 0) delete processedMetadata.undefined;
10536
+ const partitionMetadata = {
10537
+ ...processedMetadata,
10538
+ _version: this.version
10539
+ };
10540
+ if (partitionMetadata.undefined !== void 0) delete partitionMetadata.undefined;
10541
+ await this.client.putObject({
10542
+ key: newPartitionKey,
10543
+ metadata: partitionMetadata,
10544
+ body
10545
+ });
10546
+ } catch (error) {
10547
+ console.warn(`New partition object could not be created for ${partitionName}:`, error.message);
10548
+ }
10549
+ }
10550
+ } else if (newPartitionKey) {
10551
+ try {
10552
+ const mappedData = await this.schema.mapper(newData);
10553
+ if (mappedData.undefined !== void 0) delete mappedData.undefined;
10554
+ const behaviorImpl = getBehavior(this.behavior);
10555
+ const { mappedData: processedMetadata, body } = await behaviorImpl.handleUpdate({
10556
+ resource: this,
10557
+ id,
10558
+ data: newData,
10559
+ mappedData
10560
+ });
10561
+ if (processedMetadata.undefined !== void 0) delete processedMetadata.undefined;
10562
+ const partitionMetadata = {
10563
+ ...processedMetadata,
10564
+ _version: this.version
10565
+ };
10566
+ if (partitionMetadata.undefined !== void 0) delete partitionMetadata.undefined;
10567
+ await this.client.putObject({
10568
+ key: newPartitionKey,
10569
+ metadata: partitionMetadata,
10570
+ body
10571
+ });
10572
+ } catch (error) {
10573
+ console.warn(`Partition object could not be updated for ${partitionName}:`, error.message);
10574
+ }
10575
+ }
10576
+ }
10577
+ /**
10578
+ * Update partition objects to keep them in sync (legacy method for backward compatibility)
10366
10579
  * @param {Object} data - Updated object data
10367
10580
  */
10368
10581
  async updatePartitionReferences(data) {
@@ -10371,6 +10584,10 @@ ${validation.errors.join("\n")}`);
10371
10584
  return;
10372
10585
  }
10373
10586
  for (const [partitionName, partition] of Object.entries(partitions)) {
10587
+ if (!partition || !partition.fields || typeof partition.fields !== "object") {
10588
+ console.warn(`Skipping invalid partition '${partitionName}' in resource '${this.name}'`);
10589
+ continue;
10590
+ }
10374
10591
  const partitionKey = this.getPartitionKey({ partitionName, id: data.id, data });
10375
10592
  if (partitionKey) {
10376
10593
  const mappedData = await this.schema.mapper(data);
@@ -10469,6 +10686,7 @@ ${validation.errors.join("\n")}`);
10469
10686
  if (request.VersionId) data._versionId = request.VersionId;
10470
10687
  if (request.Expiration) data._expiresAt = request.Expiration;
10471
10688
  data._definitionHash = this.getDefinitionHash();
10689
+ if (data.undefined !== void 0) delete data.undefined;
10472
10690
  this.emit("getFromPartition", data);
10473
10691
  return data;
10474
10692
  }
@@ -10572,7 +10790,7 @@ ${validation.errors.join("\n")}`);
10572
10790
  this.version = "1";
10573
10791
  this.s3dbVersion = (() => {
10574
10792
  try {
10575
- return true ? "4.1.10" : "latest";
10793
+ return true ? "5.0.0" : "latest";
10576
10794
  } catch (e) {
10577
10795
  return "latest";
10578
10796
  }
@@ -10587,14 +10805,24 @@ ${validation.errors.join("\n")}`);
10587
10805
  this.passphrase = options.passphrase || "secret";
10588
10806
  let connectionString = options.connectionString;
10589
10807
  if (!connectionString && (options.bucket || options.accessKeyId || options.secretAccessKey)) {
10590
- connectionString = ConnectionString.buildFromParams({
10591
- bucket: options.bucket,
10592
- region: options.region,
10593
- accessKeyId: options.accessKeyId,
10594
- secretAccessKey: options.secretAccessKey,
10595
- endpoint: options.endpoint,
10596
- forcePathStyle: options.forcePathStyle
10597
- });
10808
+ const { bucket, region, accessKeyId, secretAccessKey, endpoint, forcePathStyle } = options;
10809
+ if (endpoint) {
10810
+ const url = new URL(endpoint);
10811
+ if (accessKeyId) url.username = encodeURIComponent(accessKeyId);
10812
+ if (secretAccessKey) url.password = encodeURIComponent(secretAccessKey);
10813
+ url.pathname = `/${bucket || "s3db"}`;
10814
+ if (forcePathStyle) {
10815
+ url.searchParams.set("forcePathStyle", "true");
10816
+ }
10817
+ connectionString = url.toString();
10818
+ } else if (accessKeyId && secretAccessKey) {
10819
+ const params = new URLSearchParams();
10820
+ params.set("region", region || "us-east-1");
10821
+ if (forcePathStyle) {
10822
+ params.set("forcePathStyle", "true");
10823
+ }
10824
+ connectionString = `s3://${encodeURIComponent(accessKeyId)}:${encodeURIComponent(secretAccessKey)}@${bucket || "s3db"}?${params.toString()}`;
10825
+ }
10598
10826
  }
10599
10827
  this.client = options.client || new Client({
10600
10828
  verbose: this.verbose,
@@ -10630,12 +10858,12 @@ ${validation.errors.join("\n")}`);
10630
10858
  passphrase: this.passphrase,
10631
10859
  observers: [this],
10632
10860
  cache: this.cache,
10633
- timestamps: versionData.options?.timestamps || false,
10634
- partitions: resourceMetadata.partitions || versionData.options?.partitions || {},
10635
- paranoid: versionData.options?.paranoid !== false,
10636
- allNestedObjectsOptional: versionData.options?.allNestedObjectsOptional || false,
10637
- autoDecrypt: versionData.options?.autoDecrypt !== false,
10638
- hooks: {}
10861
+ timestamps: versionData.timestamps !== void 0 ? versionData.timestamps : false,
10862
+ partitions: resourceMetadata.partitions || versionData.partitions || {},
10863
+ paranoid: versionData.paranoid !== void 0 ? versionData.paranoid : true,
10864
+ allNestedObjectsOptional: versionData.allNestedObjectsOptional !== void 0 ? versionData.allNestedObjectsOptional : true,
10865
+ autoDecrypt: versionData.autoDecrypt !== void 0 ? versionData.autoDecrypt : true,
10866
+ hooks: versionData.hooks || {}
10639
10867
  });
10640
10868
  }
10641
10869
  }
@@ -10773,15 +11001,14 @@ ${validation.errors.join("\n")}`);
10773
11001
  [version]: {
10774
11002
  hash: definitionHash,
10775
11003
  attributes: resourceDef.attributes,
10776
- options: {
10777
- timestamps: resource.config.timestamps,
10778
- partitions: resource.config.partitions,
10779
- paranoid: resource.config.paranoid,
10780
- allNestedObjectsOptional: resource.config.allNestedObjectsOptional,
10781
- autoDecrypt: resource.config.autoDecrypt,
10782
- cache: resource.config.cache
10783
- },
10784
11004
  behavior: resourceDef.behavior || "user-management",
11005
+ timestamps: resource.config.timestamps,
11006
+ partitions: resource.config.partitions,
11007
+ paranoid: resource.config.paranoid,
11008
+ allNestedObjectsOptional: resource.config.allNestedObjectsOptional,
11009
+ autoDecrypt: resource.config.autoDecrypt,
11010
+ cache: resource.config.cache,
11011
+ hooks: resource.config.hooks,
10785
11012
  createdAt: isNewVersion ? (/* @__PURE__ */ new Date()).toISOString() : existingVersionData?.createdAt
10786
11013
  }
10787
11014
  }
@@ -10816,73 +11043,58 @@ ${validation.errors.join("\n")}`);
10816
11043
  }
10817
11044
  /**
10818
11045
  * Check if a resource exists with the same definition hash
10819
- * @param {string} name - Resource name
10820
- * @param {Object} attributes - Resource attributes
10821
- * @param {Object} options - Resource options
10822
- * @param {string} behavior - Resource behavior
10823
- * @returns {Object} Object with exists flag and hash information
11046
+ * @param {Object} config - Resource configuration
11047
+ * @param {string} config.name - Resource name
11048
+ * @param {Object} config.attributes - Resource attributes
11049
+ * @param {string} [config.behavior] - Resource behavior
11050
+ * @param {Object} [config.options] - Resource options (deprecated, use root level parameters)
11051
+ * @returns {Object} Result with exists and hash information
10824
11052
  */
10825
- resourceExistsWithSameHash({ name, attributes, options = {}, behavior = "user-management" }) {
11053
+ resourceExistsWithSameHash({ name, attributes, behavior = "user-management", options = {} }) {
10826
11054
  if (!this.resources[name]) {
10827
11055
  return { exists: false, sameHash: false, hash: null };
10828
11056
  }
10829
- const tempResource = new Resource({
11057
+ const existingResource = this.resources[name];
11058
+ const existingHash = this.generateDefinitionHash(existingResource.export());
11059
+ const mockResource = new Resource({
10830
11060
  name,
10831
11061
  attributes,
10832
11062
  behavior,
10833
- observers: [],
10834
11063
  client: this.client,
10835
- version: "temp",
11064
+ version: existingResource.version,
10836
11065
  passphrase: this.passphrase,
10837
- cache: this.cache,
10838
11066
  ...options
10839
11067
  });
10840
- const newHash = this.generateDefinitionHash(tempResource.export(), behavior);
10841
- const existingHash = this.generateDefinitionHash(this.resources[name].export(), this.resources[name].behavior);
11068
+ const newHash = this.generateDefinitionHash(mockResource.export());
10842
11069
  return {
10843
11070
  exists: true,
10844
- sameHash: newHash === existingHash,
11071
+ sameHash: existingHash === newHash,
10845
11072
  hash: newHash,
10846
11073
  existingHash
10847
11074
  };
10848
11075
  }
10849
- /**
10850
- * Create a resource only if it doesn't exist with the same definition hash
10851
- * @param {Object} params - Resource parameters
10852
- * @param {string} params.name - Resource name
10853
- * @param {Object} params.attributes - Resource attributes
10854
- * @param {Object} params.options - Resource options
10855
- * @param {string} params.behavior - Resource behavior
10856
- * @returns {Object} Object with resource and created flag
10857
- */
10858
- async createResourceIfNotExists({ name, attributes, options = {}, behavior = "user-management" }) {
10859
- const alreadyExists = !!this.resources[name];
10860
- const hashCheck = this.resourceExistsWithSameHash({ name, attributes, options, behavior });
10861
- if (hashCheck.exists && hashCheck.sameHash) {
10862
- return {
10863
- resource: this.resources[name],
10864
- created: false,
10865
- reason: "Resource already exists with same definition hash"
10866
- };
10867
- }
10868
- const resource = await this.createResource({ name, attributes, options, behavior });
10869
- return {
10870
- resource,
10871
- created: !alreadyExists,
10872
- reason: alreadyExists ? "Resource updated with new definition" : "New resource created"
10873
- };
10874
- }
10875
- async createResource({ name, attributes, options = {}, behavior = "user-management" }) {
11076
+ async createResource({ name, attributes, behavior = "user-management", hooks, ...config }) {
10876
11077
  if (this.resources[name]) {
10877
11078
  const existingResource = this.resources[name];
10878
11079
  Object.assign(existingResource.config, {
10879
11080
  cache: this.cache,
10880
- ...options
11081
+ ...config
10881
11082
  });
10882
11083
  if (behavior) {
10883
11084
  existingResource.behavior = behavior;
10884
11085
  }
10885
11086
  existingResource.updateAttributes(attributes);
11087
+ if (hooks) {
11088
+ for (const [event, hooksArr] of Object.entries(hooks)) {
11089
+ if (Array.isArray(hooksArr) && existingResource.hooks[event]) {
11090
+ for (const fn of hooksArr) {
11091
+ if (typeof fn === "function") {
11092
+ existingResource.hooks[event].push(fn.bind(existingResource));
11093
+ }
11094
+ }
11095
+ }
11096
+ }
11097
+ }
10886
11098
  const newHash = this.generateDefinitionHash(existingResource.export(), existingResource.behavior);
10887
11099
  const existingMetadata2 = this.savedMetadata?.resources?.[name];
10888
11100
  const currentVersion = existingMetadata2?.currentVersion || "v0";
@@ -10904,7 +11116,8 @@ ${validation.errors.join("\n")}`);
10904
11116
  version,
10905
11117
  passphrase: this.passphrase,
10906
11118
  cache: this.cache,
10907
- ...options
11119
+ hooks,
11120
+ ...config
10908
11121
  });
10909
11122
  this.resources[name] = resource;
10910
11123
  await this.uploadMetadataFile();
@@ -17606,6 +17819,7 @@ ${validation.errors.join("\n")}`);
17606
17819
  }
17607
17820
  }
17608
17821
 
17822
+ exports.AVAILABLE_BEHAVIORS = AVAILABLE_BEHAVIORS;
17609
17823
  exports.AuthenticationError = AuthenticationError;
17610
17824
  exports.BaseError = BaseError;
17611
17825
  exports.Cache = Cache;
@@ -17613,6 +17827,7 @@ ${validation.errors.join("\n")}`);
17613
17827
  exports.Client = Client;
17614
17828
  exports.ConnectionString = ConnectionString;
17615
17829
  exports.CostsPlugin = CostsPlugin;
17830
+ exports.DEFAULT_BEHAVIOR = DEFAULT_BEHAVIOR;
17616
17831
  exports.Database = Database;
17617
17832
  exports.DatabaseError = DatabaseError;
17618
17833
  exports.EncryptionError = EncryptionError;
@@ -17626,28 +17841,38 @@ ${validation.errors.join("\n")}`);
17626
17841
  exports.PermissionError = PermissionError;
17627
17842
  exports.Plugin = Plugin;
17628
17843
  exports.PluginObject = PluginObject;
17844
+ exports.Resource = Resource;
17629
17845
  exports.ResourceIdsPageReader = ResourceIdsPageReader;
17630
17846
  exports.ResourceIdsReader = ResourceIdsReader;
17631
17847
  exports.ResourceNotFound = ResourceNotFound;
17632
- exports.ResourceNotFoundError = ResourceNotFound;
17633
17848
  exports.ResourceReader = ResourceReader;
17634
17849
  exports.ResourceWriter = ResourceWriter;
17635
17850
  exports.S3Cache = S3Cache;
17636
- exports.S3DB = S3db;
17637
17851
  exports.S3DBError = S3DBError;
17638
17852
  exports.S3_DEFAULT_ENDPOINT = S3_DEFAULT_ENDPOINT;
17639
17853
  exports.S3_DEFAULT_REGION = S3_DEFAULT_REGION;
17640
17854
  exports.S3db = S3db;
17641
- exports.S3dbError = S3DBError;
17855
+ exports.Schema = Schema;
17856
+ exports.SchemaActions = SchemaActions;
17642
17857
  exports.UnknownError = UnknownError;
17643
17858
  exports.ValidationError = ValidationError;
17644
17859
  exports.Validator = Validator;
17645
17860
  exports.ValidatorManager = ValidatorManager;
17861
+ exports.behaviors = behaviors;
17862
+ exports.calculateAttributeNamesSize = calculateAttributeNamesSize;
17863
+ exports.calculateAttributeSizes = calculateAttributeSizes;
17864
+ exports.calculateTotalSize = calculateTotalSize;
17865
+ exports.calculateUTF8Bytes = calculateUTF8Bytes;
17646
17866
  exports.decrypt = decrypt;
17647
17867
  exports.default = S3db;
17648
17868
  exports.encrypt = encrypt;
17869
+ exports.getBehavior = getBehavior;
17870
+ exports.getSizeBreakdown = getSizeBreakdown;
17871
+ exports.idGenerator = idGenerator;
17872
+ exports.passwordGenerator = passwordGenerator;
17649
17873
  exports.sha256 = sha256;
17650
17874
  exports.streamToString = streamToString;
17875
+ exports.transformValue = transformValue;
17651
17876
 
17652
17877
  Object.defineProperty(exports, '__esModule', { value: true });
17653
17878