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.cjs.js CHANGED
@@ -247,7 +247,7 @@ var substr = 'ab'.substr(-1) === 'b' ?
247
247
 
248
248
  const idGenerator = nanoid.customAlphabet(nanoid.urlAlphabet, 22);
249
249
  const passwordAlphabet = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789";
250
- nanoid.customAlphabet(passwordAlphabet, 12);
250
+ const passwordGenerator = nanoid.customAlphabet(passwordAlphabet, 12);
251
251
 
252
252
  var domain;
253
253
 
@@ -3392,7 +3392,7 @@ function requireJsonStableStringify () {
3392
3392
  var jsonStableStringifyExports = requireJsonStableStringify();
3393
3393
  var jsonStableStringify = /*@__PURE__*/getDefaultExportFromCjs(jsonStableStringifyExports);
3394
3394
 
3395
- async function custom(actual, errors, schema) {
3395
+ async function secretHandler(actual, errors, schema) {
3396
3396
  if (!this.passphrase) {
3397
3397
  errors.push({ actual, type: "encryptionKeyMissing" });
3398
3398
  return actual;
@@ -3405,6 +3405,10 @@ async function custom(actual, errors, schema) {
3405
3405
  }
3406
3406
  return actual;
3407
3407
  }
3408
+ async function jsonHandler(actual, errors, schema) {
3409
+ if (lodashEs.isString(actual)) return actual;
3410
+ return JSON.stringify(actual);
3411
+ }
3408
3412
  class Validator extends FastestValidator {
3409
3413
  constructor({ options, passphrase, autoEncrypt = true } = {}) {
3410
3414
  super(lodashEs.merge({}, {
@@ -3426,7 +3430,7 @@ class Validator extends FastestValidator {
3426
3430
  this.autoEncrypt = autoEncrypt;
3427
3431
  this.alias("secret", {
3428
3432
  type: "string",
3429
- custom: this.autoEncrypt ? custom : void 0,
3433
+ custom: this.autoEncrypt ? secretHandler : void 0,
3430
3434
  messages: {
3431
3435
  string: "The '{field}' field must be a string.",
3432
3436
  stringMin: "This secret '{field}' field length must be at least {expected} long."
@@ -3434,11 +3438,15 @@ class Validator extends FastestValidator {
3434
3438
  });
3435
3439
  this.alias("secretAny", {
3436
3440
  type: "any",
3437
- custom: this.autoEncrypt ? custom : void 0
3441
+ custom: this.autoEncrypt ? secretHandler : void 0
3438
3442
  });
3439
3443
  this.alias("secretNumber", {
3440
3444
  type: "number",
3441
- custom: this.autoEncrypt ? custom : void 0
3445
+ custom: this.autoEncrypt ? secretHandler : void 0
3446
+ });
3447
+ this.alias("json", {
3448
+ type: "any",
3449
+ custom: this.autoEncrypt ? jsonHandler : void 0
3442
3450
  });
3443
3451
  }
3444
3452
  }
@@ -3450,10 +3458,31 @@ const ValidatorManager = new Proxy(Validator, {
3450
3458
  }
3451
3459
  });
3452
3460
 
3461
+ function toBase36(num) {
3462
+ return num.toString(36);
3463
+ }
3464
+ function generateBase36Mapping(keys) {
3465
+ const mapping = {};
3466
+ const reversedMapping = {};
3467
+ keys.forEach((key, index) => {
3468
+ const base36Key = toBase36(index);
3469
+ mapping[key] = base36Key;
3470
+ reversedMapping[base36Key] = key;
3471
+ });
3472
+ return { mapping, reversedMapping };
3473
+ }
3453
3474
  const SchemaActions = {
3454
3475
  trim: (value) => value.trim(),
3455
3476
  encrypt: (value, { passphrase }) => encrypt(value, passphrase),
3456
- decrypt: (value, { passphrase }) => decrypt(value, passphrase),
3477
+ decrypt: async (value, { passphrase }) => {
3478
+ try {
3479
+ const raw = await decrypt(value, passphrase);
3480
+ return raw;
3481
+ } catch (error) {
3482
+ console.warn(`Schema decrypt error: ${error}`, error);
3483
+ return value;
3484
+ }
3485
+ },
3457
3486
  toString: (value) => String(value),
3458
3487
  fromArray: (value, { separator }) => {
3459
3488
  if (value === null || value === void 0 || !Array.isArray(value)) {
@@ -3544,8 +3573,9 @@ class Schema {
3544
3573
  const leafKeys = Object.keys(flatAttrs).filter((k) => !k.includes("$$"));
3545
3574
  const objectKeys = this.extractObjectKeys(this.attributes);
3546
3575
  const allKeys = [.../* @__PURE__ */ new Set([...leafKeys, ...objectKeys])];
3547
- this.reversedMap = { ...allKeys };
3548
- this.map = lodashEs.invert(this.reversedMap);
3576
+ const { mapping, reversedMapping } = generateBase36Mapping(allKeys);
3577
+ this.map = mapping;
3578
+ this.reversedMap = reversedMapping;
3549
3579
  }
3550
3580
  }
3551
3581
  defaultOptions() {
@@ -3586,24 +3616,27 @@ class Schema {
3586
3616
  if (definition.includes("array")) {
3587
3617
  this.addHook("beforeMap", name, "fromArray");
3588
3618
  this.addHook("afterUnmap", name, "toArray");
3589
- } else {
3590
- if (definition.includes("secret")) {
3591
- if (this.options.autoEncrypt) {
3592
- this.addHook("beforeMap", name, "encrypt");
3593
- }
3594
- if (this.options.autoDecrypt) {
3595
- this.addHook("afterUnmap", name, "decrypt");
3596
- }
3597
- }
3598
- if (definition.includes("number")) {
3599
- this.addHook("beforeMap", name, "toString");
3600
- this.addHook("afterUnmap", name, "toNumber");
3619
+ }
3620
+ if (definition.includes("secret")) {
3621
+ if (this.options.autoEncrypt) {
3622
+ this.addHook("beforeMap", name, "encrypt");
3601
3623
  }
3602
- if (definition.includes("boolean")) {
3603
- this.addHook("beforeMap", name, "fromBool");
3604
- this.addHook("afterUnmap", name, "toBool");
3624
+ if (this.options.autoDecrypt) {
3625
+ this.addHook("afterUnmap", name, "decrypt");
3605
3626
  }
3606
3627
  }
3628
+ if (definition.includes("number")) {
3629
+ this.addHook("beforeMap", name, "toString");
3630
+ this.addHook("afterUnmap", name, "toNumber");
3631
+ }
3632
+ if (definition.includes("boolean")) {
3633
+ this.addHook("beforeMap", name, "fromBool");
3634
+ this.addHook("afterUnmap", name, "toBool");
3635
+ }
3636
+ if (definition.includes("json")) {
3637
+ this.addHook("beforeMap", name, "toJSON");
3638
+ this.addHook("afterUnmap", name, "fromJSON");
3639
+ }
3607
3640
  }
3608
3641
  }
3609
3642
  static import(data) {
@@ -8618,7 +8651,6 @@ function calculateUTF8Bytes(str) {
8618
8651
  function calculateAttributeNamesSize(mappedObject) {
8619
8652
  let totalSize = 0;
8620
8653
  for (const key of Object.keys(mappedObject)) {
8621
- if (key === "_v") continue;
8622
8654
  totalSize += calculateUTF8Bytes(key);
8623
8655
  }
8624
8656
  return totalSize;
@@ -8662,6 +8694,30 @@ function calculateTotalSize(mappedObject) {
8662
8694
  const namesSize = calculateAttributeNamesSize(mappedObject);
8663
8695
  return valueTotal + namesSize;
8664
8696
  }
8697
+ function getSizeBreakdown(mappedObject) {
8698
+ const valueSizes = calculateAttributeSizes(mappedObject);
8699
+ const namesSize = calculateAttributeNamesSize(mappedObject);
8700
+ const valueTotal = Object.values(valueSizes).reduce((sum, size) => sum + size, 0);
8701
+ const total = valueTotal + namesSize;
8702
+ const sortedAttributes = Object.entries(valueSizes).sort(([, a], [, b]) => b - a).map(([key, size]) => ({
8703
+ attribute: key,
8704
+ size,
8705
+ percentage: (size / total * 100).toFixed(2) + "%"
8706
+ }));
8707
+ return {
8708
+ total,
8709
+ valueSizes,
8710
+ namesSize,
8711
+ valueTotal,
8712
+ breakdown: sortedAttributes,
8713
+ // Add detailed breakdown including names
8714
+ detailedBreakdown: {
8715
+ values: valueTotal,
8716
+ names: namesSize,
8717
+ total
8718
+ }
8719
+ };
8720
+ }
8665
8721
 
8666
8722
  const S3_METADATA_LIMIT_BYTES = 2048;
8667
8723
  async function handleInsert$3({ resource, data, mappedData }) {
@@ -8882,6 +8938,7 @@ function getBehavior(behaviorName) {
8882
8938
  }
8883
8939
  return behavior;
8884
8940
  }
8941
+ const AVAILABLE_BEHAVIORS = Object.keys(behaviors);
8885
8942
  const DEFAULT_BEHAVIOR = "user-management";
8886
8943
 
8887
8944
  class Resource extends EventEmitter {
@@ -8951,17 +9008,8 @@ ${validation.errors.join("\n")}`);
8951
9008
  partitions = {},
8952
9009
  paranoid = true,
8953
9010
  allNestedObjectsOptional = true,
8954
- hooks = {},
8955
- options = {}
9011
+ hooks = {}
8956
9012
  } = config;
8957
- const mergedOptions = {
8958
- cache: typeof options.cache === "boolean" ? options.cache : cache,
8959
- autoDecrypt: typeof options.autoDecrypt === "boolean" ? options.autoDecrypt : autoDecrypt,
8960
- timestamps: typeof options.timestamps === "boolean" ? options.timestamps : timestamps,
8961
- paranoid: typeof options.paranoid === "boolean" ? options.paranoid : paranoid,
8962
- allNestedObjectsOptional: typeof options.allNestedObjectsOptional === "boolean" ? options.allNestedObjectsOptional : allNestedObjectsOptional,
8963
- partitions: options.partitions || partitions || {}
8964
- };
8965
9013
  this.name = name;
8966
9014
  this.client = client;
8967
9015
  this.version = version;
@@ -8970,13 +9018,13 @@ ${validation.errors.join("\n")}`);
8970
9018
  this.parallelism = parallelism;
8971
9019
  this.passphrase = passphrase ?? "secret";
8972
9020
  this.config = {
8973
- cache: mergedOptions.cache,
9021
+ cache,
8974
9022
  hooks,
8975
- paranoid: mergedOptions.paranoid,
8976
- timestamps: mergedOptions.timestamps,
8977
- partitions: mergedOptions.partitions,
8978
- autoDecrypt: mergedOptions.autoDecrypt,
8979
- allNestedObjectsOptional: mergedOptions.allNestedObjectsOptional
9023
+ paranoid,
9024
+ timestamps,
9025
+ partitions,
9026
+ autoDecrypt,
9027
+ allNestedObjectsOptional
8980
9028
  };
8981
9029
  this.hooks = {
8982
9030
  preInsert: [],
@@ -8987,39 +9035,7 @@ ${validation.errors.join("\n")}`);
8987
9035
  afterDelete: []
8988
9036
  };
8989
9037
  this.attributes = attributes || {};
8990
- if (this.config.timestamps) {
8991
- this.attributes.createdAt = "string|optional";
8992
- this.attributes.updatedAt = "string|optional";
8993
- if (!this.config.partitions) {
8994
- this.config.partitions = {};
8995
- }
8996
- if (!this.config.partitions.byCreatedDate) {
8997
- this.config.partitions.byCreatedDate = {
8998
- fields: {
8999
- createdAt: "date|maxlength:10"
9000
- }
9001
- };
9002
- }
9003
- if (!this.config.partitions.byUpdatedDate) {
9004
- this.config.partitions.byUpdatedDate = {
9005
- fields: {
9006
- updatedAt: "date|maxlength:10"
9007
- }
9008
- };
9009
- }
9010
- }
9011
- this.schema = new Schema({
9012
- name,
9013
- attributes: this.attributes,
9014
- passphrase,
9015
- version: this.version,
9016
- options: {
9017
- autoDecrypt: this.config.autoDecrypt,
9018
- allNestedObjectsOptional: this.config.allNestedObjectsOptional
9019
- }
9020
- });
9021
- this.setupPartitionHooks();
9022
- this.validatePartitions();
9038
+ this.applyConfiguration();
9023
9039
  if (hooks) {
9024
9040
  for (const [event, hooksArr] of Object.entries(hooks)) {
9025
9041
  if (Array.isArray(hooksArr) && this.hooks[event]) {
@@ -9058,15 +9074,17 @@ ${validation.errors.join("\n")}`);
9058
9074
  return exported;
9059
9075
  }
9060
9076
  /**
9061
- * Update resource attributes and rebuild schema
9062
- * @param {Object} newAttributes - New attributes definition
9077
+ * Apply configuration settings (timestamps, partitions, hooks)
9078
+ * This method ensures that all configuration-dependent features are properly set up
9063
9079
  */
9064
- updateAttributes(newAttributes) {
9065
- const oldAttributes = this.attributes;
9066
- this.attributes = newAttributes;
9080
+ applyConfiguration() {
9067
9081
  if (this.config.timestamps) {
9068
- newAttributes.createdAt = "string|optional";
9069
- newAttributes.updatedAt = "string|optional";
9082
+ if (!this.attributes.createdAt) {
9083
+ this.attributes.createdAt = "string|optional";
9084
+ }
9085
+ if (!this.attributes.updatedAt) {
9086
+ this.attributes.updatedAt = "string|optional";
9087
+ }
9070
9088
  if (!this.config.partitions) {
9071
9089
  this.config.partitions = {};
9072
9090
  }
@@ -9085,9 +9103,10 @@ ${validation.errors.join("\n")}`);
9085
9103
  };
9086
9104
  }
9087
9105
  }
9106
+ this.setupPartitionHooks();
9088
9107
  this.schema = new Schema({
9089
9108
  name: this.name,
9090
- attributes: newAttributes,
9109
+ attributes: this.attributes,
9091
9110
  passphrase: this.passphrase,
9092
9111
  version: this.version,
9093
9112
  options: {
@@ -9095,8 +9114,16 @@ ${validation.errors.join("\n")}`);
9095
9114
  allNestedObjectsOptional: this.config.allNestedObjectsOptional
9096
9115
  }
9097
9116
  });
9098
- this.setupPartitionHooks();
9099
9117
  this.validatePartitions();
9118
+ }
9119
+ /**
9120
+ * Update resource attributes and rebuild schema
9121
+ * @param {Object} newAttributes - New attributes definition
9122
+ */
9123
+ updateAttributes(newAttributes) {
9124
+ const oldAttributes = this.attributes;
9125
+ this.attributes = newAttributes;
9126
+ this.applyConfiguration();
9100
9127
  return { oldAttributes, newAttributes };
9101
9128
  }
9102
9129
  /**
@@ -9184,7 +9211,7 @@ ${validation.errors.join("\n")}`);
9184
9211
  for (const fieldName of Object.keys(partitionDef.fields)) {
9185
9212
  if (!this.fieldExistsInAttributes(fieldName)) {
9186
9213
  throw new Error(
9187
- `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.`
9214
+ `Partition '${partitionName}' uses field '${fieldName}' which does not exist in resource attributes. Available fields: ${currentAttributes.join(", ")}.`
9188
9215
  );
9189
9216
  }
9190
9217
  }
@@ -9297,7 +9324,11 @@ ${validation.errors.join("\n")}`);
9297
9324
  if (partitionSegments.length === 0) {
9298
9325
  return null;
9299
9326
  }
9300
- return join(`resource=${this.name}`, `partition=${partitionName}`, ...partitionSegments, `id=${id}`);
9327
+ const finalId = id || data?.id;
9328
+ if (!finalId) {
9329
+ return null;
9330
+ }
9331
+ return join(`resource=${this.name}`, `partition=${partitionName}`, ...partitionSegments, `id=${finalId}`);
9301
9332
  }
9302
9333
  /**
9303
9334
  * Get nested field value from data object using dot notation
@@ -9526,12 +9557,12 @@ ${validation.errors.join("\n")}`);
9526
9557
  * console.log(updatedUser.updatedAt); // ISO timestamp
9527
9558
  */
9528
9559
  async update(id, attributes) {
9529
- const live = await this.get(id);
9560
+ const originalData = await this.get(id);
9530
9561
  if (this.config.timestamps) {
9531
9562
  attributes.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
9532
9563
  }
9533
9564
  const preProcessedData = await this.executeHooks("preUpdate", attributes);
9534
- const attrs = lodashEs.merge(live, preProcessedData);
9565
+ const attrs = lodashEs.merge(originalData, preProcessedData);
9535
9566
  delete attrs.id;
9536
9567
  const { isValid, errors, data: validated } = await this.validate(attrs);
9537
9568
  if (!isValid) {
@@ -9542,6 +9573,9 @@ ${validation.errors.join("\n")}`);
9542
9573
  validation: errors
9543
9574
  });
9544
9575
  }
9576
+ const oldData = { ...originalData, id };
9577
+ const newData = { ...validated, id };
9578
+ await this.handlePartitionReferenceUpdates(oldData, newData);
9545
9579
  const mappedData = await this.schema.mapper(validated);
9546
9580
  const behaviorImpl = getBehavior(this.behavior);
9547
9581
  const { mappedData: processedMetadata, body } = await behaviorImpl.handleUpdate({
@@ -9577,7 +9611,6 @@ ${validation.errors.join("\n")}`);
9577
9611
  });
9578
9612
  validated.id = id;
9579
9613
  await this.executeHooks("afterUpdate", validated);
9580
- await this.updatePartitionReferences(validated);
9581
9614
  this.emit("update", preProcessedData, validated);
9582
9615
  return validated;
9583
9616
  }
@@ -9864,23 +9897,104 @@ ${validation.errors.join("\n")}`);
9864
9897
  * });
9865
9898
  */
9866
9899
  async list({ partition = null, partitionValues = {}, limit, offset = 0 } = {}) {
9867
- if (!partition) {
9868
- const ids2 = await this.listIds({ partition, partitionValues });
9869
- let filteredIds2 = ids2.slice(offset);
9900
+ try {
9901
+ if (!partition) {
9902
+ let ids2 = [];
9903
+ try {
9904
+ ids2 = await this.listIds({ partition, partitionValues });
9905
+ } catch (listIdsError) {
9906
+ console.warn(`Failed to get list IDs:`, listIdsError.message);
9907
+ return [];
9908
+ }
9909
+ let filteredIds2 = ids2.slice(offset);
9910
+ if (limit) {
9911
+ filteredIds2 = filteredIds2.slice(0, limit);
9912
+ }
9913
+ const { results: results2, errors: errors2 } = await promisePool.PromisePool.for(filteredIds2).withConcurrency(this.parallelism).handleError(async (error, id) => {
9914
+ console.warn(`Failed to get resource ${id}:`, error.message);
9915
+ return null;
9916
+ }).process(async (id) => {
9917
+ try {
9918
+ return await this.get(id);
9919
+ } catch (error) {
9920
+ if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
9921
+ console.warn(`Decryption failed for ${id}, returning basic info`);
9922
+ return {
9923
+ id,
9924
+ _decryptionFailed: true,
9925
+ _error: error.message
9926
+ };
9927
+ }
9928
+ throw error;
9929
+ }
9930
+ });
9931
+ const validResults2 = results2.filter((item) => item !== null);
9932
+ this.emit("list", { partition, partitionValues, count: validResults2.length, errors: errors2.length });
9933
+ return validResults2;
9934
+ }
9935
+ if (!this.config.partitions || !this.config.partitions[partition]) {
9936
+ console.warn(`Partition '${partition}' not found in resource '${this.name}'`);
9937
+ this.emit("list", { partition, partitionValues, count: 0, errors: 0 });
9938
+ return [];
9939
+ }
9940
+ const partitionDef = this.config.partitions[partition];
9941
+ const partitionSegments = [];
9942
+ const sortedFields = Object.entries(partitionDef.fields).sort(([a], [b]) => a.localeCompare(b));
9943
+ for (const [fieldName, rule] of sortedFields) {
9944
+ const value = partitionValues[fieldName];
9945
+ if (value !== void 0 && value !== null) {
9946
+ const transformedValue = this.applyPartitionRule(value, rule);
9947
+ partitionSegments.push(`${fieldName}=${transformedValue}`);
9948
+ }
9949
+ }
9950
+ let prefix;
9951
+ if (partitionSegments.length > 0) {
9952
+ prefix = `resource=${this.name}/partition=${partition}/${partitionSegments.join("/")}`;
9953
+ } else {
9954
+ prefix = `resource=${this.name}/partition=${partition}`;
9955
+ }
9956
+ let keys = [];
9957
+ try {
9958
+ keys = await this.client.getAllKeys({ prefix });
9959
+ } catch (getKeysError) {
9960
+ console.warn(`Failed to get partition keys:`, getKeysError.message);
9961
+ return [];
9962
+ }
9963
+ const ids = keys.map((key) => {
9964
+ const parts = key.split("/");
9965
+ const idPart = parts.find((part) => part.startsWith("id="));
9966
+ return idPart ? idPart.replace("id=", "") : null;
9967
+ }).filter(Boolean);
9968
+ let filteredIds = ids.slice(offset);
9870
9969
  if (limit) {
9871
- filteredIds2 = filteredIds2.slice(0, limit);
9970
+ filteredIds = filteredIds.slice(0, limit);
9872
9971
  }
9873
- const { results: results2, errors: errors2 } = await promisePool.PromisePool.for(filteredIds2).withConcurrency(this.parallelism).handleError(async (error, id) => {
9874
- console.warn(`Failed to get resource ${id}:`, error.message);
9972
+ const { results, errors } = await promisePool.PromisePool.for(filteredIds).withConcurrency(this.parallelism).handleError(async (error, id) => {
9973
+ console.warn(`Failed to get partition resource ${id}:`, error.message);
9875
9974
  return null;
9876
9975
  }).process(async (id) => {
9877
9976
  try {
9878
- return await this.get(id);
9977
+ const keyForId = keys.find((key) => key.includes(`id=${id}`));
9978
+ if (!keyForId) {
9979
+ throw new Error(`Partition key not found for ID ${id}`);
9980
+ }
9981
+ const keyParts = keyForId.split("/");
9982
+ const actualPartitionValues = {};
9983
+ for (const [fieldName, rule] of sortedFields) {
9984
+ const fieldPart = keyParts.find((part) => part.startsWith(`${fieldName}=`));
9985
+ if (fieldPart) {
9986
+ const value = fieldPart.replace(`${fieldName}=`, "");
9987
+ actualPartitionValues[fieldName] = value;
9988
+ }
9989
+ }
9990
+ return await this.getFromPartition({ id, partitionName: partition, partitionValues: actualPartitionValues });
9879
9991
  } catch (error) {
9880
9992
  if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
9881
- console.warn(`Decryption failed for ${id}, returning basic info`);
9993
+ console.warn(`Decryption failed for partition resource ${id}, returning basic info`);
9882
9994
  return {
9883
9995
  id,
9996
+ _partition: partition,
9997
+ _partitionValues: partitionValues,
9884
9998
  _decryptionFailed: true,
9885
9999
  _error: error.message
9886
10000
  };
@@ -9888,62 +10002,19 @@ ${validation.errors.join("\n")}`);
9888
10002
  throw error;
9889
10003
  }
9890
10004
  });
9891
- const validResults2 = results2.filter((item) => item !== null);
9892
- this.emit("list", { partition, partitionValues, count: validResults2.length, errors: errors2.length });
9893
- return validResults2;
9894
- }
9895
- if (!this.config.partitions || !this.config.partitions[partition]) {
9896
- throw new Error(`Partition '${partition}' not found`);
9897
- }
9898
- const partitionDef = this.config.partitions[partition];
9899
- const partitionSegments = [];
9900
- const sortedFields = Object.entries(partitionDef.fields).sort(([a], [b]) => a.localeCompare(b));
9901
- for (const [fieldName, rule] of sortedFields) {
9902
- const value = partitionValues[fieldName];
9903
- if (value !== void 0 && value !== null) {
9904
- const transformedValue = this.applyPartitionRule(value, rule);
9905
- partitionSegments.push(`${fieldName}=${transformedValue}`);
10005
+ const validResults = results.filter((item) => item !== null);
10006
+ this.emit("list", { partition, partitionValues, count: validResults.length, errors: errors.length });
10007
+ return validResults;
10008
+ } catch (error) {
10009
+ if (error.message.includes("Partition '") && error.message.includes("' not found")) {
10010
+ console.warn(`Partition error in list method:`, error.message);
10011
+ this.emit("list", { partition, partitionValues, count: 0, errors: 1 });
10012
+ return [];
9906
10013
  }
10014
+ console.error(`Critical error in list method:`, error.message);
10015
+ this.emit("list", { partition, partitionValues, count: 0, errors: 1 });
10016
+ return [];
9907
10017
  }
9908
- let prefix;
9909
- if (partitionSegments.length > 0) {
9910
- prefix = `resource=${this.name}/partition=${partition}/${partitionSegments.join("/")}`;
9911
- } else {
9912
- prefix = `resource=${this.name}/partition=${partition}`;
9913
- }
9914
- const keys = await this.client.getAllKeys({ prefix });
9915
- const ids = keys.map((key) => {
9916
- const parts = key.split("/");
9917
- const idPart = parts.find((part) => part.startsWith("id="));
9918
- return idPart ? idPart.replace("id=", "") : null;
9919
- }).filter(Boolean);
9920
- let filteredIds = ids.slice(offset);
9921
- if (limit) {
9922
- filteredIds = filteredIds.slice(0, limit);
9923
- }
9924
- const { results, errors } = await promisePool.PromisePool.for(filteredIds).withConcurrency(this.parallelism).handleError(async (error, id) => {
9925
- console.warn(`Failed to get partition resource ${id}:`, error.message);
9926
- return null;
9927
- }).process(async (id) => {
9928
- try {
9929
- return await this.getFromPartition({ id, partitionName: partition, partitionValues });
9930
- } catch (error) {
9931
- if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
9932
- console.warn(`Decryption failed for partition resource ${id}, returning basic info`);
9933
- return {
9934
- id,
9935
- _partition: partition,
9936
- _partitionValues: partitionValues,
9937
- _decryptionFailed: true,
9938
- _error: error.message
9939
- };
9940
- }
9941
- throw error;
9942
- }
9943
- });
9944
- const validResults = results.filter((item) => item !== null);
9945
- this.emit("list", { partition, partitionValues, count: validResults.length, errors: errors.length });
9946
- return validResults;
9947
10018
  }
9948
10019
  /**
9949
10020
  * Get multiple resources by their IDs
@@ -10050,36 +10121,67 @@ ${validation.errors.join("\n")}`);
10050
10121
  * console.log(`Got ${fastPage.items.length} items`); // totalItems will be null
10051
10122
  */
10052
10123
  async page({ offset = 0, size = 100, partition = null, partitionValues = {}, skipCount = false } = {}) {
10053
- let totalItems = null;
10054
- let totalPages = null;
10055
- if (!skipCount) {
10056
- totalItems = await this.count({ partition, partitionValues });
10057
- totalPages = Math.ceil(totalItems / size);
10058
- }
10059
- const page = Math.floor(offset / size);
10060
- const items = await this.list({
10061
- partition,
10062
- partitionValues,
10063
- limit: size,
10064
- offset
10065
- });
10066
- const result = {
10067
- items,
10068
- totalItems,
10069
- page,
10070
- pageSize: size,
10071
- totalPages,
10072
- // Add additional metadata for debugging
10073
- _debug: {
10074
- requestedSize: size,
10075
- requestedOffset: offset,
10076
- actualItemsReturned: items.length,
10077
- skipCount,
10078
- hasTotalItems: totalItems !== null
10124
+ try {
10125
+ let totalItems = null;
10126
+ let totalPages = null;
10127
+ if (!skipCount) {
10128
+ try {
10129
+ totalItems = await this.count({ partition, partitionValues });
10130
+ totalPages = Math.ceil(totalItems / size);
10131
+ } catch (countError) {
10132
+ console.warn(`Failed to get count for page:`, countError.message);
10133
+ totalItems = null;
10134
+ totalPages = null;
10135
+ }
10079
10136
  }
10080
- };
10081
- this.emit("page", result);
10082
- return result;
10137
+ const page = Math.floor(offset / size);
10138
+ let items = [];
10139
+ try {
10140
+ items = await this.list({
10141
+ partition,
10142
+ partitionValues,
10143
+ limit: size,
10144
+ offset
10145
+ });
10146
+ } catch (listError) {
10147
+ console.warn(`Failed to get items for page:`, listError.message);
10148
+ items = [];
10149
+ }
10150
+ const result = {
10151
+ items,
10152
+ totalItems,
10153
+ page,
10154
+ pageSize: size,
10155
+ totalPages,
10156
+ // Add additional metadata for debugging
10157
+ _debug: {
10158
+ requestedSize: size,
10159
+ requestedOffset: offset,
10160
+ actualItemsReturned: items.length,
10161
+ skipCount,
10162
+ hasTotalItems: totalItems !== null
10163
+ }
10164
+ };
10165
+ this.emit("page", result);
10166
+ return result;
10167
+ } catch (error) {
10168
+ console.error(`Critical error in page method:`, error.message);
10169
+ return {
10170
+ items: [],
10171
+ totalItems: null,
10172
+ page: Math.floor(offset / size),
10173
+ pageSize: size,
10174
+ totalPages: null,
10175
+ _debug: {
10176
+ requestedSize: size,
10177
+ requestedOffset: offset,
10178
+ actualItemsReturned: 0,
10179
+ skipCount,
10180
+ hasTotalItems: false,
10181
+ error: error.message
10182
+ }
10183
+ };
10184
+ }
10083
10185
  }
10084
10186
  readable() {
10085
10187
  const stream = new ResourceReader({ resource: this });
@@ -10372,7 +10474,118 @@ ${validation.errors.join("\n")}`);
10372
10474
  return results.slice(0, limit);
10373
10475
  }
10374
10476
  /**
10375
- * Update partition objects to keep them in sync
10477
+ * Handle partition reference updates with change detection
10478
+ * @param {Object} oldData - Original object data before update
10479
+ * @param {Object} newData - Updated object data
10480
+ */
10481
+ async handlePartitionReferenceUpdates(oldData, newData) {
10482
+ const partitions = this.config.partitions;
10483
+ if (!partitions || Object.keys(partitions).length === 0) {
10484
+ return;
10485
+ }
10486
+ for (const [partitionName, partition] of Object.entries(partitions)) {
10487
+ try {
10488
+ await this.handlePartitionReferenceUpdate(partitionName, partition, oldData, newData);
10489
+ } catch (error) {
10490
+ console.warn(`Failed to update partition references for ${partitionName}:`, error.message);
10491
+ }
10492
+ }
10493
+ const id = newData.id || oldData.id;
10494
+ for (const [partitionName, partition] of Object.entries(partitions)) {
10495
+ const prefix = `resource=${this.name}/partition=${partitionName}`;
10496
+ let allKeys = [];
10497
+ try {
10498
+ allKeys = await this.client.getAllKeys({ prefix });
10499
+ } catch (error) {
10500
+ console.warn(`Aggressive cleanup: could not list keys for partition ${partitionName}:`, error.message);
10501
+ continue;
10502
+ }
10503
+ const validKey = this.getPartitionKey({ partitionName, id, data: newData });
10504
+ for (const key of allKeys) {
10505
+ if (key.endsWith(`/id=${id}`) && key !== validKey) {
10506
+ try {
10507
+ await this.client.deleteObject(key);
10508
+ } catch (error) {
10509
+ console.warn(`Aggressive cleanup: could not delete stale partition key ${key}:`, error.message);
10510
+ }
10511
+ }
10512
+ }
10513
+ }
10514
+ }
10515
+ /**
10516
+ * Handle partition reference update for a specific partition
10517
+ * @param {string} partitionName - Name of the partition
10518
+ * @param {Object} partition - Partition definition
10519
+ * @param {Object} oldData - Original object data before update
10520
+ * @param {Object} newData - Updated object data
10521
+ */
10522
+ async handlePartitionReferenceUpdate(partitionName, partition, oldData, newData) {
10523
+ const id = newData.id || oldData.id;
10524
+ const oldPartitionKey = this.getPartitionKey({ partitionName, id, data: oldData });
10525
+ const newPartitionKey = this.getPartitionKey({ partitionName, id, data: newData });
10526
+ if (oldPartitionKey !== newPartitionKey) {
10527
+ if (oldPartitionKey) {
10528
+ try {
10529
+ await this.client.deleteObject(oldPartitionKey);
10530
+ } catch (error) {
10531
+ console.warn(`Old partition object could not be deleted for ${partitionName}:`, error.message);
10532
+ }
10533
+ }
10534
+ if (newPartitionKey) {
10535
+ try {
10536
+ const mappedData = await this.schema.mapper(newData);
10537
+ if (mappedData.undefined !== void 0) delete mappedData.undefined;
10538
+ const behaviorImpl = getBehavior(this.behavior);
10539
+ const { mappedData: processedMetadata, body } = await behaviorImpl.handleUpdate({
10540
+ resource: this,
10541
+ id,
10542
+ data: newData,
10543
+ mappedData
10544
+ });
10545
+ if (processedMetadata.undefined !== void 0) delete processedMetadata.undefined;
10546
+ const partitionMetadata = {
10547
+ ...processedMetadata,
10548
+ _version: this.version
10549
+ };
10550
+ if (partitionMetadata.undefined !== void 0) delete partitionMetadata.undefined;
10551
+ await this.client.putObject({
10552
+ key: newPartitionKey,
10553
+ metadata: partitionMetadata,
10554
+ body
10555
+ });
10556
+ } catch (error) {
10557
+ console.warn(`New partition object could not be created for ${partitionName}:`, error.message);
10558
+ }
10559
+ }
10560
+ } else if (newPartitionKey) {
10561
+ try {
10562
+ const mappedData = await this.schema.mapper(newData);
10563
+ if (mappedData.undefined !== void 0) delete mappedData.undefined;
10564
+ const behaviorImpl = getBehavior(this.behavior);
10565
+ const { mappedData: processedMetadata, body } = await behaviorImpl.handleUpdate({
10566
+ resource: this,
10567
+ id,
10568
+ data: newData,
10569
+ mappedData
10570
+ });
10571
+ if (processedMetadata.undefined !== void 0) delete processedMetadata.undefined;
10572
+ const partitionMetadata = {
10573
+ ...processedMetadata,
10574
+ _version: this.version
10575
+ };
10576
+ if (partitionMetadata.undefined !== void 0) delete partitionMetadata.undefined;
10577
+ await this.client.putObject({
10578
+ key: newPartitionKey,
10579
+ metadata: partitionMetadata,
10580
+ body
10581
+ });
10582
+ } catch (error) {
10583
+ console.warn(`Partition object could not be updated for ${partitionName}:`, error.message);
10584
+ }
10585
+ }
10586
+ }
10587
+ /**
10588
+ * Update partition objects to keep them in sync (legacy method for backward compatibility)
10376
10589
  * @param {Object} data - Updated object data
10377
10590
  */
10378
10591
  async updatePartitionReferences(data) {
@@ -10381,6 +10594,10 @@ ${validation.errors.join("\n")}`);
10381
10594
  return;
10382
10595
  }
10383
10596
  for (const [partitionName, partition] of Object.entries(partitions)) {
10597
+ if (!partition || !partition.fields || typeof partition.fields !== "object") {
10598
+ console.warn(`Skipping invalid partition '${partitionName}' in resource '${this.name}'`);
10599
+ continue;
10600
+ }
10384
10601
  const partitionKey = this.getPartitionKey({ partitionName, id: data.id, data });
10385
10602
  if (partitionKey) {
10386
10603
  const mappedData = await this.schema.mapper(data);
@@ -10479,6 +10696,7 @@ ${validation.errors.join("\n")}`);
10479
10696
  if (request.VersionId) data._versionId = request.VersionId;
10480
10697
  if (request.Expiration) data._expiresAt = request.Expiration;
10481
10698
  data._definitionHash = this.getDefinitionHash();
10699
+ if (data.undefined !== void 0) delete data.undefined;
10482
10700
  this.emit("getFromPartition", data);
10483
10701
  return data;
10484
10702
  }
@@ -10582,7 +10800,7 @@ class Database extends EventEmitter {
10582
10800
  this.version = "1";
10583
10801
  this.s3dbVersion = (() => {
10584
10802
  try {
10585
- return true ? "4.1.10" : "latest";
10803
+ return true ? "5.0.0" : "latest";
10586
10804
  } catch (e) {
10587
10805
  return "latest";
10588
10806
  }
@@ -10597,14 +10815,24 @@ class Database extends EventEmitter {
10597
10815
  this.passphrase = options.passphrase || "secret";
10598
10816
  let connectionString = options.connectionString;
10599
10817
  if (!connectionString && (options.bucket || options.accessKeyId || options.secretAccessKey)) {
10600
- connectionString = ConnectionString.buildFromParams({
10601
- bucket: options.bucket,
10602
- region: options.region,
10603
- accessKeyId: options.accessKeyId,
10604
- secretAccessKey: options.secretAccessKey,
10605
- endpoint: options.endpoint,
10606
- forcePathStyle: options.forcePathStyle
10607
- });
10818
+ const { bucket, region, accessKeyId, secretAccessKey, endpoint, forcePathStyle } = options;
10819
+ if (endpoint) {
10820
+ const url = new URL(endpoint);
10821
+ if (accessKeyId) url.username = encodeURIComponent(accessKeyId);
10822
+ if (secretAccessKey) url.password = encodeURIComponent(secretAccessKey);
10823
+ url.pathname = `/${bucket || "s3db"}`;
10824
+ if (forcePathStyle) {
10825
+ url.searchParams.set("forcePathStyle", "true");
10826
+ }
10827
+ connectionString = url.toString();
10828
+ } else if (accessKeyId && secretAccessKey) {
10829
+ const params = new URLSearchParams();
10830
+ params.set("region", region || "us-east-1");
10831
+ if (forcePathStyle) {
10832
+ params.set("forcePathStyle", "true");
10833
+ }
10834
+ connectionString = `s3://${encodeURIComponent(accessKeyId)}:${encodeURIComponent(secretAccessKey)}@${bucket || "s3db"}?${params.toString()}`;
10835
+ }
10608
10836
  }
10609
10837
  this.client = options.client || new Client({
10610
10838
  verbose: this.verbose,
@@ -10640,12 +10868,12 @@ class Database extends EventEmitter {
10640
10868
  passphrase: this.passphrase,
10641
10869
  observers: [this],
10642
10870
  cache: this.cache,
10643
- timestamps: versionData.options?.timestamps || false,
10644
- partitions: resourceMetadata.partitions || versionData.options?.partitions || {},
10645
- paranoid: versionData.options?.paranoid !== false,
10646
- allNestedObjectsOptional: versionData.options?.allNestedObjectsOptional || false,
10647
- autoDecrypt: versionData.options?.autoDecrypt !== false,
10648
- hooks: {}
10871
+ timestamps: versionData.timestamps !== void 0 ? versionData.timestamps : false,
10872
+ partitions: resourceMetadata.partitions || versionData.partitions || {},
10873
+ paranoid: versionData.paranoid !== void 0 ? versionData.paranoid : true,
10874
+ allNestedObjectsOptional: versionData.allNestedObjectsOptional !== void 0 ? versionData.allNestedObjectsOptional : true,
10875
+ autoDecrypt: versionData.autoDecrypt !== void 0 ? versionData.autoDecrypt : true,
10876
+ hooks: versionData.hooks || {}
10649
10877
  });
10650
10878
  }
10651
10879
  }
@@ -10783,15 +11011,14 @@ class Database extends EventEmitter {
10783
11011
  [version]: {
10784
11012
  hash: definitionHash,
10785
11013
  attributes: resourceDef.attributes,
10786
- options: {
10787
- timestamps: resource.config.timestamps,
10788
- partitions: resource.config.partitions,
10789
- paranoid: resource.config.paranoid,
10790
- allNestedObjectsOptional: resource.config.allNestedObjectsOptional,
10791
- autoDecrypt: resource.config.autoDecrypt,
10792
- cache: resource.config.cache
10793
- },
10794
11014
  behavior: resourceDef.behavior || "user-management",
11015
+ timestamps: resource.config.timestamps,
11016
+ partitions: resource.config.partitions,
11017
+ paranoid: resource.config.paranoid,
11018
+ allNestedObjectsOptional: resource.config.allNestedObjectsOptional,
11019
+ autoDecrypt: resource.config.autoDecrypt,
11020
+ cache: resource.config.cache,
11021
+ hooks: resource.config.hooks,
10795
11022
  createdAt: isNewVersion ? (/* @__PURE__ */ new Date()).toISOString() : existingVersionData?.createdAt
10796
11023
  }
10797
11024
  }
@@ -10826,73 +11053,58 @@ class Database extends EventEmitter {
10826
11053
  }
10827
11054
  /**
10828
11055
  * Check if a resource exists with the same definition hash
10829
- * @param {string} name - Resource name
10830
- * @param {Object} attributes - Resource attributes
10831
- * @param {Object} options - Resource options
10832
- * @param {string} behavior - Resource behavior
10833
- * @returns {Object} Object with exists flag and hash information
11056
+ * @param {Object} config - Resource configuration
11057
+ * @param {string} config.name - Resource name
11058
+ * @param {Object} config.attributes - Resource attributes
11059
+ * @param {string} [config.behavior] - Resource behavior
11060
+ * @param {Object} [config.options] - Resource options (deprecated, use root level parameters)
11061
+ * @returns {Object} Result with exists and hash information
10834
11062
  */
10835
- resourceExistsWithSameHash({ name, attributes, options = {}, behavior = "user-management" }) {
11063
+ resourceExistsWithSameHash({ name, attributes, behavior = "user-management", options = {} }) {
10836
11064
  if (!this.resources[name]) {
10837
11065
  return { exists: false, sameHash: false, hash: null };
10838
11066
  }
10839
- const tempResource = new Resource({
11067
+ const existingResource = this.resources[name];
11068
+ const existingHash = this.generateDefinitionHash(existingResource.export());
11069
+ const mockResource = new Resource({
10840
11070
  name,
10841
11071
  attributes,
10842
11072
  behavior,
10843
- observers: [],
10844
11073
  client: this.client,
10845
- version: "temp",
11074
+ version: existingResource.version,
10846
11075
  passphrase: this.passphrase,
10847
- cache: this.cache,
10848
11076
  ...options
10849
11077
  });
10850
- const newHash = this.generateDefinitionHash(tempResource.export(), behavior);
10851
- const existingHash = this.generateDefinitionHash(this.resources[name].export(), this.resources[name].behavior);
11078
+ const newHash = this.generateDefinitionHash(mockResource.export());
10852
11079
  return {
10853
11080
  exists: true,
10854
- sameHash: newHash === existingHash,
11081
+ sameHash: existingHash === newHash,
10855
11082
  hash: newHash,
10856
11083
  existingHash
10857
11084
  };
10858
11085
  }
10859
- /**
10860
- * Create a resource only if it doesn't exist with the same definition hash
10861
- * @param {Object} params - Resource parameters
10862
- * @param {string} params.name - Resource name
10863
- * @param {Object} params.attributes - Resource attributes
10864
- * @param {Object} params.options - Resource options
10865
- * @param {string} params.behavior - Resource behavior
10866
- * @returns {Object} Object with resource and created flag
10867
- */
10868
- async createResourceIfNotExists({ name, attributes, options = {}, behavior = "user-management" }) {
10869
- const alreadyExists = !!this.resources[name];
10870
- const hashCheck = this.resourceExistsWithSameHash({ name, attributes, options, behavior });
10871
- if (hashCheck.exists && hashCheck.sameHash) {
10872
- return {
10873
- resource: this.resources[name],
10874
- created: false,
10875
- reason: "Resource already exists with same definition hash"
10876
- };
10877
- }
10878
- const resource = await this.createResource({ name, attributes, options, behavior });
10879
- return {
10880
- resource,
10881
- created: !alreadyExists,
10882
- reason: alreadyExists ? "Resource updated with new definition" : "New resource created"
10883
- };
10884
- }
10885
- async createResource({ name, attributes, options = {}, behavior = "user-management" }) {
11086
+ async createResource({ name, attributes, behavior = "user-management", hooks, ...config }) {
10886
11087
  if (this.resources[name]) {
10887
11088
  const existingResource = this.resources[name];
10888
11089
  Object.assign(existingResource.config, {
10889
11090
  cache: this.cache,
10890
- ...options
11091
+ ...config
10891
11092
  });
10892
11093
  if (behavior) {
10893
11094
  existingResource.behavior = behavior;
10894
11095
  }
10895
11096
  existingResource.updateAttributes(attributes);
11097
+ if (hooks) {
11098
+ for (const [event, hooksArr] of Object.entries(hooks)) {
11099
+ if (Array.isArray(hooksArr) && existingResource.hooks[event]) {
11100
+ for (const fn of hooksArr) {
11101
+ if (typeof fn === "function") {
11102
+ existingResource.hooks[event].push(fn.bind(existingResource));
11103
+ }
11104
+ }
11105
+ }
11106
+ }
11107
+ }
10896
11108
  const newHash = this.generateDefinitionHash(existingResource.export(), existingResource.behavior);
10897
11109
  const existingMetadata2 = this.savedMetadata?.resources?.[name];
10898
11110
  const currentVersion = existingMetadata2?.currentVersion || "v0";
@@ -10914,7 +11126,8 @@ class Database extends EventEmitter {
10914
11126
  version,
10915
11127
  passphrase: this.passphrase,
10916
11128
  cache: this.cache,
10917
- ...options
11129
+ hooks,
11130
+ ...config
10918
11131
  });
10919
11132
  this.resources[name] = resource;
10920
11133
  await this.uploadMetadataFile();
@@ -17616,6 +17829,7 @@ class CachePlugin extends Plugin {
17616
17829
  }
17617
17830
  }
17618
17831
 
17832
+ exports.AVAILABLE_BEHAVIORS = AVAILABLE_BEHAVIORS;
17619
17833
  exports.AuthenticationError = AuthenticationError;
17620
17834
  exports.BaseError = BaseError;
17621
17835
  exports.Cache = Cache;
@@ -17623,6 +17837,7 @@ exports.CachePlugin = CachePlugin;
17623
17837
  exports.Client = Client;
17624
17838
  exports.ConnectionString = ConnectionString;
17625
17839
  exports.CostsPlugin = CostsPlugin;
17840
+ exports.DEFAULT_BEHAVIOR = DEFAULT_BEHAVIOR;
17626
17841
  exports.Database = Database;
17627
17842
  exports.DatabaseError = DatabaseError;
17628
17843
  exports.EncryptionError = EncryptionError;
@@ -17636,25 +17851,35 @@ exports.NotFound = NotFound;
17636
17851
  exports.PermissionError = PermissionError;
17637
17852
  exports.Plugin = Plugin;
17638
17853
  exports.PluginObject = PluginObject;
17854
+ exports.Resource = Resource;
17639
17855
  exports.ResourceIdsPageReader = ResourceIdsPageReader;
17640
17856
  exports.ResourceIdsReader = ResourceIdsReader;
17641
17857
  exports.ResourceNotFound = ResourceNotFound;
17642
- exports.ResourceNotFoundError = ResourceNotFound;
17643
17858
  exports.ResourceReader = ResourceReader;
17644
17859
  exports.ResourceWriter = ResourceWriter;
17645
17860
  exports.S3Cache = S3Cache;
17646
- exports.S3DB = S3db;
17647
17861
  exports.S3DBError = S3DBError;
17648
17862
  exports.S3_DEFAULT_ENDPOINT = S3_DEFAULT_ENDPOINT;
17649
17863
  exports.S3_DEFAULT_REGION = S3_DEFAULT_REGION;
17650
17864
  exports.S3db = S3db;
17651
- exports.S3dbError = S3DBError;
17865
+ exports.Schema = Schema;
17866
+ exports.SchemaActions = SchemaActions;
17652
17867
  exports.UnknownError = UnknownError;
17653
17868
  exports.ValidationError = ValidationError;
17654
17869
  exports.Validator = Validator;
17655
17870
  exports.ValidatorManager = ValidatorManager;
17871
+ exports.behaviors = behaviors;
17872
+ exports.calculateAttributeNamesSize = calculateAttributeNamesSize;
17873
+ exports.calculateAttributeSizes = calculateAttributeSizes;
17874
+ exports.calculateTotalSize = calculateTotalSize;
17875
+ exports.calculateUTF8Bytes = calculateUTF8Bytes;
17656
17876
  exports.decrypt = decrypt;
17657
17877
  exports.default = S3db;
17658
17878
  exports.encrypt = encrypt;
17879
+ exports.getBehavior = getBehavior;
17880
+ exports.getSizeBreakdown = getSizeBreakdown;
17881
+ exports.idGenerator = idGenerator;
17882
+ exports.passwordGenerator = passwordGenerator;
17659
17883
  exports.sha256 = sha256;
17660
17884
  exports.streamToString = streamToString;
17885
+ exports.transformValue = transformValue;