s3db.js 4.1.8 → 4.1.11

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
@@ -1,6 +1,8 @@
1
1
  /* istanbul ignore file */
2
2
  'use strict';
3
3
 
4
+ Object.defineProperty(exports, '__esModule', { value: true });
5
+
4
6
  var nanoid = require('nanoid');
5
7
  var lodashEs = require('lodash-es');
6
8
  var promisePool = require('@supercharge/promise-pool');
@@ -244,6 +246,8 @@ var substr = 'ab'.substr(-1) === 'b' ?
244
246
  ;
245
247
 
246
248
  const idGenerator = nanoid.customAlphabet(nanoid.urlAlphabet, 22);
249
+ const passwordAlphabet = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789";
250
+ nanoid.customAlphabet(passwordAlphabet, 12);
247
251
 
248
252
  var domain;
249
253
 
@@ -744,42 +748,85 @@ ${JSON.stringify(rest, null, 2)}`;
744
748
  return `${this.name} | ${this.message}`;
745
749
  }
746
750
  }
747
- class NoSuchBucket extends BaseError {
751
+ class S3DBError extends BaseError {
752
+ constructor(message, details = {}) {
753
+ super({ message, ...details });
754
+ }
755
+ }
756
+ class DatabaseError extends S3DBError {
757
+ constructor(message, details = {}) {
758
+ super(message, details);
759
+ Object.assign(this, details);
760
+ }
761
+ }
762
+ class ValidationError extends S3DBError {
763
+ constructor(message, details = {}) {
764
+ super(message, details);
765
+ Object.assign(this, details);
766
+ }
767
+ }
768
+ class AuthenticationError extends S3DBError {
769
+ constructor(message, details = {}) {
770
+ super(message, details);
771
+ Object.assign(this, details);
772
+ }
773
+ }
774
+ class PermissionError extends S3DBError {
775
+ constructor(message, details = {}) {
776
+ super(message, details);
777
+ Object.assign(this, details);
778
+ }
779
+ }
780
+ class EncryptionError extends S3DBError {
781
+ constructor(message, details = {}) {
782
+ super(message, details);
783
+ Object.assign(this, details);
784
+ }
785
+ }
786
+ class ResourceNotFound extends S3DBError {
787
+ constructor({ bucket, resourceName, id, ...rest }) {
788
+ super(`Resource not found: ${resourceName}/${id} [bucket:${bucket}]`, {
789
+ bucket,
790
+ resourceName,
791
+ id,
792
+ ...rest
793
+ });
794
+ }
795
+ }
796
+ class NoSuchBucket extends S3DBError {
748
797
  constructor({ bucket, ...rest }) {
749
- super({ ...rest, bucket, message: `Bucket does not exists [bucket:${bucket}]` });
798
+ super(`Bucket does not exists [bucket:${bucket}]`, { bucket, ...rest });
750
799
  }
751
800
  }
752
- class NoSuchKey extends BaseError {
801
+ class NoSuchKey extends S3DBError {
753
802
  constructor({ bucket, key, ...rest }) {
754
- super({ ...rest, bucket, message: `Key [${key}] does not exists [bucket:${bucket}/${key}]` });
755
- this.key = key;
803
+ super(`Key [${key}] does not exists [bucket:${bucket}/${key}]`, { bucket, key, ...rest });
756
804
  }
757
805
  }
758
806
  class NotFound extends NoSuchKey {
759
807
  }
760
- class MissingMetadata extends BaseError {
808
+ class MissingMetadata extends S3DBError {
761
809
  constructor({ bucket, ...rest }) {
762
- super({ ...rest, bucket, message: `Missing metadata for bucket [bucket:${bucket}]` });
810
+ super(`Missing metadata for bucket [bucket:${bucket}]`, { bucket, ...rest });
763
811
  }
764
812
  }
765
- class InvalidResourceItem extends BaseError {
813
+ class InvalidResourceItem extends S3DBError {
766
814
  constructor({
767
815
  bucket,
768
816
  resourceName,
769
817
  attributes,
770
818
  validation
771
819
  }) {
772
- super({
820
+ super(`This item is not valid. Resource=${resourceName} [bucket:${bucket}].
821
+ ${JSON.stringify(validation, null, 2)}`, {
773
822
  bucket,
774
- message: `This item is not valid. Resource=${resourceName} [bucket:${bucket}].
775
- ${JSON.stringify(validation, null, 2)}`
823
+ resourceName,
824
+ attributes,
825
+ validation
776
826
  });
777
- this.resourceName = resourceName;
778
- this.attributes = attributes;
779
- this.validation = validation;
780
827
  }
781
828
  }
782
- class UnknownError extends BaseError {
829
+ class UnknownError extends S3DBError {
783
830
  }
784
831
  const ErrorMap = {
785
832
  "NotFound": NotFound,
@@ -808,9 +855,9 @@ class ConnectionString {
808
855
  }
809
856
  }
810
857
  defineS3(uri) {
811
- this.bucket = uri.hostname;
812
- this.accessKeyId = uri.username;
813
- this.secretAccessKey = uri.password;
858
+ this.bucket = decodeURIComponent(uri.hostname);
859
+ this.accessKeyId = decodeURIComponent(uri.username);
860
+ this.secretAccessKey = decodeURIComponent(uri.password);
814
861
  this.endpoint = S3_DEFAULT_ENDPOINT;
815
862
  if (["/", "", null].includes(uri.pathname)) {
816
863
  this.keyPrefix = "";
@@ -822,14 +869,14 @@ class ConnectionString {
822
869
  defineMinio(uri) {
823
870
  this.forcePathStyle = true;
824
871
  this.endpoint = uri.origin;
825
- this.accessKeyId = uri.username;
826
- this.secretAccessKey = uri.password;
872
+ this.accessKeyId = decodeURIComponent(uri.username);
873
+ this.secretAccessKey = decodeURIComponent(uri.password);
827
874
  if (["/", "", null].includes(uri.pathname)) {
828
875
  this.bucket = "s3db";
829
876
  this.keyPrefix = "";
830
877
  } else {
831
878
  let [, bucket, ...subpath] = uri.pathname.split("/");
832
- this.bucket = bucket;
879
+ this.bucket = decodeURIComponent(bucket);
833
880
  this.keyPrefix = [...subpath || []].join("/");
834
881
  }
835
882
  }
@@ -8838,18 +8885,83 @@ function getBehavior(behaviorName) {
8838
8885
  const DEFAULT_BEHAVIOR = "user-management";
8839
8886
 
8840
8887
  class Resource extends EventEmitter {
8841
- constructor({
8842
- name,
8843
- client,
8844
- version = "1",
8845
- options = {},
8846
- attributes = {},
8847
- parallelism = 10,
8848
- passphrase = "secret",
8849
- observers = [],
8850
- behavior = DEFAULT_BEHAVIOR
8851
- }) {
8888
+ /**
8889
+ * Create a new Resource instance
8890
+ * @param {Object} config - Resource configuration
8891
+ * @param {string} config.name - Resource name
8892
+ * @param {Object} config.client - S3 client instance
8893
+ * @param {string} [config.version='v0'] - Resource version
8894
+ * @param {Object} [config.attributes={}] - Resource attributes schema
8895
+ * @param {string} [config.behavior='user-management'] - Resource behavior strategy
8896
+ * @param {string} [config.passphrase='secret'] - Encryption passphrase
8897
+ * @param {number} [config.parallelism=10] - Parallelism for bulk operations
8898
+ * @param {Array} [config.observers=[]] - Observer instances
8899
+ * @param {boolean} [config.cache=false] - Enable caching
8900
+ * @param {boolean} [config.autoDecrypt=true] - Auto-decrypt secret fields
8901
+ * @param {boolean} [config.timestamps=false] - Enable automatic timestamps
8902
+ * @param {Object} [config.partitions={}] - Partition definitions
8903
+ * @param {boolean} [config.paranoid=true] - Security flag for dangerous operations
8904
+ * @param {boolean} [config.allNestedObjectsOptional=false] - Make nested objects optional
8905
+ * @param {Object} [config.hooks={}] - Custom hooks
8906
+ * @param {Object} [config.options={}] - Additional options
8907
+ * @example
8908
+ * const users = new Resource({
8909
+ * name: 'users',
8910
+ * client: s3Client,
8911
+ * attributes: {
8912
+ * name: 'string|required',
8913
+ * email: 'string|required',
8914
+ * password: 'secret|required'
8915
+ * },
8916
+ * behavior: 'user-management',
8917
+ * passphrase: 'my-secret-key',
8918
+ * timestamps: true,
8919
+ * partitions: {
8920
+ * byRegion: {
8921
+ * fields: { region: 'string' }
8922
+ * }
8923
+ * },
8924
+ * hooks: {
8925
+ * preInsert: [async (data) => {
8926
+ * console.log('Pre-insert hook:', data);
8927
+ * return data;
8928
+ * }]
8929
+ * }
8930
+ * });
8931
+ */
8932
+ constructor(config) {
8852
8933
  super();
8934
+ const validation = validateResourceConfig(config);
8935
+ if (!validation.isValid) {
8936
+ throw new Error(`Invalid Resource configuration:
8937
+ ${validation.errors.join("\n")}`);
8938
+ }
8939
+ const {
8940
+ name,
8941
+ client,
8942
+ version = "1",
8943
+ attributes = {},
8944
+ behavior = DEFAULT_BEHAVIOR,
8945
+ passphrase = "secret",
8946
+ parallelism = 10,
8947
+ observers = [],
8948
+ cache = false,
8949
+ autoDecrypt = true,
8950
+ timestamps = false,
8951
+ partitions = {},
8952
+ paranoid = true,
8953
+ allNestedObjectsOptional = true,
8954
+ hooks = {},
8955
+ options = {}
8956
+ } = 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
+ };
8853
8965
  this.name = name;
8854
8966
  this.client = client;
8855
8967
  this.version = version;
@@ -8857,15 +8969,14 @@ class Resource extends EventEmitter {
8857
8969
  this.observers = observers;
8858
8970
  this.parallelism = parallelism;
8859
8971
  this.passphrase = passphrase ?? "secret";
8860
- this.options = {
8861
- cache: false,
8862
- autoDecrypt: true,
8863
- timestamps: false,
8864
- partitions: {},
8865
- paranoid: true,
8866
- // Security flag for dangerous operations
8867
- allNestedObjectsOptional: options.allNestedObjectsOptional ?? false,
8868
- ...options
8972
+ this.config = {
8973
+ cache: mergedOptions.cache,
8974
+ hooks,
8975
+ paranoid: mergedOptions.paranoid,
8976
+ timestamps: mergedOptions.timestamps,
8977
+ partitions: mergedOptions.partitions,
8978
+ autoDecrypt: mergedOptions.autoDecrypt,
8979
+ allNestedObjectsOptional: mergedOptions.allNestedObjectsOptional
8869
8980
  };
8870
8981
  this.hooks = {
8871
8982
  preInsert: [],
@@ -8876,18 +8987,21 @@ class Resource extends EventEmitter {
8876
8987
  afterDelete: []
8877
8988
  };
8878
8989
  this.attributes = attributes || {};
8879
- if (options.timestamps) {
8990
+ if (this.config.timestamps) {
8880
8991
  this.attributes.createdAt = "string|optional";
8881
8992
  this.attributes.updatedAt = "string|optional";
8882
- if (!this.options.partitions.byCreatedDate) {
8883
- this.options.partitions.byCreatedDate = {
8993
+ if (!this.config.partitions) {
8994
+ this.config.partitions = {};
8995
+ }
8996
+ if (!this.config.partitions.byCreatedDate) {
8997
+ this.config.partitions.byCreatedDate = {
8884
8998
  fields: {
8885
8999
  createdAt: "date|maxlength:10"
8886
9000
  }
8887
9001
  };
8888
9002
  }
8889
- if (!this.options.partitions.byUpdatedDate) {
8890
- this.options.partitions.byUpdatedDate = {
9003
+ if (!this.config.partitions.byUpdatedDate) {
9004
+ this.config.partitions.byUpdatedDate = {
8891
9005
  fields: {
8892
9006
  updatedAt: "date|maxlength:10"
8893
9007
  }
@@ -8900,25 +9014,47 @@ class Resource extends EventEmitter {
8900
9014
  passphrase,
8901
9015
  version: this.version,
8902
9016
  options: {
8903
- ...this.options,
8904
- allNestedObjectsOptional: this.options.allNestedObjectsOptional ?? false
9017
+ autoDecrypt: this.config.autoDecrypt,
9018
+ allNestedObjectsOptional: this.config.allNestedObjectsOptional
8905
9019
  }
8906
9020
  });
8907
- this.validatePartitions();
8908
9021
  this.setupPartitionHooks();
8909
- if (options.hooks) {
8910
- for (const [event, hooksArr] of Object.entries(options.hooks)) {
9022
+ this.validatePartitions();
9023
+ if (hooks) {
9024
+ for (const [event, hooksArr] of Object.entries(hooks)) {
8911
9025
  if (Array.isArray(hooksArr) && this.hooks[event]) {
8912
9026
  for (const fn of hooksArr) {
8913
- this.hooks[event].push(fn.bind(this));
9027
+ if (typeof fn === "function") {
9028
+ this.hooks[event].push(fn.bind(this));
9029
+ }
8914
9030
  }
8915
9031
  }
8916
9032
  }
8917
9033
  }
8918
9034
  }
9035
+ /**
9036
+ * Get resource options (for backward compatibility with tests)
9037
+ */
9038
+ get options() {
9039
+ return {
9040
+ timestamps: this.config.timestamps,
9041
+ partitions: this.config.partitions || {},
9042
+ cache: this.config.cache,
9043
+ autoDecrypt: this.config.autoDecrypt,
9044
+ paranoid: this.config.paranoid,
9045
+ allNestedObjectsOptional: this.config.allNestedObjectsOptional
9046
+ };
9047
+ }
8919
9048
  export() {
8920
9049
  const exported = this.schema.export();
8921
9050
  exported.behavior = this.behavior;
9051
+ exported.timestamps = this.config.timestamps;
9052
+ exported.partitions = this.config.partitions || {};
9053
+ exported.paranoid = this.config.paranoid;
9054
+ exported.allNestedObjectsOptional = this.config.allNestedObjectsOptional;
9055
+ exported.autoDecrypt = this.config.autoDecrypt;
9056
+ exported.cache = this.config.cache;
9057
+ exported.hooks = this.hooks;
8922
9058
  return exported;
8923
9059
  }
8924
9060
  /**
@@ -8928,18 +9064,21 @@ class Resource extends EventEmitter {
8928
9064
  updateAttributes(newAttributes) {
8929
9065
  const oldAttributes = this.attributes;
8930
9066
  this.attributes = newAttributes;
8931
- if (this.options.timestamps) {
9067
+ if (this.config.timestamps) {
8932
9068
  newAttributes.createdAt = "string|optional";
8933
9069
  newAttributes.updatedAt = "string|optional";
8934
- if (!this.options.partitions.byCreatedDate) {
8935
- this.options.partitions.byCreatedDate = {
9070
+ if (!this.config.partitions) {
9071
+ this.config.partitions = {};
9072
+ }
9073
+ if (!this.config.partitions.byCreatedDate) {
9074
+ this.config.partitions.byCreatedDate = {
8936
9075
  fields: {
8937
9076
  createdAt: "date|maxlength:10"
8938
9077
  }
8939
9078
  };
8940
9079
  }
8941
- if (!this.options.partitions.byUpdatedDate) {
8942
- this.options.partitions.byUpdatedDate = {
9080
+ if (!this.config.partitions.byUpdatedDate) {
9081
+ this.config.partitions.byUpdatedDate = {
8943
9082
  fields: {
8944
9083
  updatedAt: "date|maxlength:10"
8945
9084
  }
@@ -8951,10 +9090,13 @@ class Resource extends EventEmitter {
8951
9090
  attributes: newAttributes,
8952
9091
  passphrase: this.passphrase,
8953
9092
  version: this.version,
8954
- options: this.options
9093
+ options: {
9094
+ autoDecrypt: this.config.autoDecrypt,
9095
+ allNestedObjectsOptional: this.config.allNestedObjectsOptional
9096
+ }
8955
9097
  });
8956
- this.validatePartitions();
8957
9098
  this.setupPartitionHooks();
9099
+ this.validatePartitions();
8958
9100
  return { oldAttributes, newAttributes };
8959
9101
  }
8960
9102
  /**
@@ -8985,15 +9127,24 @@ class Resource extends EventEmitter {
8985
9127
  * Setup automatic partition hooks
8986
9128
  */
8987
9129
  setupPartitionHooks() {
8988
- const partitions = this.options.partitions;
8989
- if (!partitions || Object.keys(partitions).length === 0) {
9130
+ if (!this.config.partitions) {
9131
+ return;
9132
+ }
9133
+ const partitions = this.config.partitions;
9134
+ if (Object.keys(partitions).length === 0) {
8990
9135
  return;
8991
9136
  }
8992
- this.addHook("afterInsert", async (data) => {
9137
+ if (!this.hooks.afterInsert) {
9138
+ this.hooks.afterInsert = [];
9139
+ }
9140
+ this.hooks.afterInsert.push(async (data) => {
8993
9141
  await this.createPartitionReferences(data);
8994
9142
  return data;
8995
9143
  });
8996
- this.addHook("afterDelete", async (data) => {
9144
+ if (!this.hooks.afterDelete) {
9145
+ this.hooks.afterDelete = [];
9146
+ }
9147
+ this.hooks.afterDelete.push(async (data) => {
8997
9148
  await this.deletePartitionReferences(data);
8998
9149
  return data;
8999
9150
  });
@@ -9018,8 +9169,11 @@ class Resource extends EventEmitter {
9018
9169
  * @throws {Error} If partition fields don't exist in current schema
9019
9170
  */
9020
9171
  validatePartitions() {
9021
- const partitions = this.options.partitions;
9022
- if (!partitions || Object.keys(partitions).length === 0) {
9172
+ if (!this.config.partitions) {
9173
+ return;
9174
+ }
9175
+ const partitions = this.config.partitions;
9176
+ if (Object.keys(partitions).length === 0) {
9023
9177
  return;
9024
9178
  }
9025
9179
  const currentAttributes = Object.keys(this.attributes || {});
@@ -9126,10 +9280,10 @@ class Resource extends EventEmitter {
9126
9280
  * // Returns: null
9127
9281
  */
9128
9282
  getPartitionKey({ partitionName, id, data }) {
9129
- const partition = this.options.partitions[partitionName];
9130
- if (!partition) {
9283
+ if (!this.config.partitions || !this.config.partitions[partitionName]) {
9131
9284
  throw new Error(`Partition '${partitionName}' not found`);
9132
9285
  }
9286
+ const partition = this.config.partitions[partitionName];
9133
9287
  const partitionSegments = [];
9134
9288
  const sortedFields = Object.entries(partition.fields).sort(([a], [b]) => a.localeCompare(b));
9135
9289
  for (const [fieldName, rule] of sortedFields) {
@@ -9185,6 +9339,12 @@ class Resource extends EventEmitter {
9185
9339
  * name: 'Jane Smith',
9186
9340
  * email: 'jane@example.com'
9187
9341
  * });
9342
+ *
9343
+ * // Insert with auto-generated password for secret field
9344
+ * const user = await resource.insert({
9345
+ * name: 'John Doe',
9346
+ * email: 'john@example.com',
9347
+ * });
9188
9348
  */
9189
9349
  async insert({ id, ...attributes }) {
9190
9350
  if (this.options.timestamps) {
@@ -9269,6 +9429,58 @@ class Resource extends EventEmitter {
9269
9429
  this.emit("get", data);
9270
9430
  return data;
9271
9431
  } catch (error) {
9432
+ if (error.message.includes("Cipher job failed") || error.message.includes("OperationError") || error.originalError?.message?.includes("Cipher job failed")) {
9433
+ try {
9434
+ console.warn(`Decryption failed for resource ${id}, attempting to get raw metadata`);
9435
+ const request = await this.client.headObject(key);
9436
+ const objectVersion = this.extractVersionFromKey(key) || this.version;
9437
+ const tempSchema = new Schema({
9438
+ name: this.name,
9439
+ attributes: this.attributes,
9440
+ passphrase: this.passphrase,
9441
+ version: objectVersion,
9442
+ options: {
9443
+ ...this.config,
9444
+ autoDecrypt: false,
9445
+ // Disable decryption
9446
+ autoEncrypt: false
9447
+ // Disable encryption
9448
+ }
9449
+ });
9450
+ let metadata = await tempSchema.unmapper(request.Metadata);
9451
+ const behaviorImpl = getBehavior(this.behavior);
9452
+ let body = "";
9453
+ if (request.ContentLength > 0) {
9454
+ try {
9455
+ const fullObject = await this.client.getObject(key);
9456
+ body = await streamToString(fullObject.Body);
9457
+ } catch (bodyError) {
9458
+ console.warn(`Failed to read body for resource ${id}:`, bodyError.message);
9459
+ body = "";
9460
+ }
9461
+ }
9462
+ const { metadata: processedMetadata } = await behaviorImpl.handleGet({
9463
+ resource: this,
9464
+ metadata,
9465
+ body
9466
+ });
9467
+ let data = processedMetadata;
9468
+ data.id = id;
9469
+ data._contentLength = request.ContentLength;
9470
+ data._lastModified = request.LastModified;
9471
+ data._hasContent = request.ContentLength > 0;
9472
+ data._mimeType = request.ContentType || null;
9473
+ data._version = objectVersion;
9474
+ data._decryptionFailed = true;
9475
+ if (request.VersionId) data._versionId = request.VersionId;
9476
+ if (request.Expiration) data._expiresAt = request.Expiration;
9477
+ data._definitionHash = this.getDefinitionHash();
9478
+ this.emit("get", data);
9479
+ return data;
9480
+ } catch (fallbackError) {
9481
+ console.error(`Fallback attempt also failed for resource ${id}:`, fallbackError.message);
9482
+ }
9483
+ }
9272
9484
  const enhancedError = new Error(`Failed to get resource with id '${id}': ${error.message}`);
9273
9485
  enhancedError.originalError = error;
9274
9486
  enhancedError.resourceId = id;
@@ -9315,7 +9527,7 @@ class Resource extends EventEmitter {
9315
9527
  */
9316
9528
  async update(id, attributes) {
9317
9529
  const live = await this.get(id);
9318
- if (this.options.timestamps) {
9530
+ if (this.config.timestamps) {
9319
9531
  attributes.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
9320
9532
  }
9321
9533
  const preProcessedData = await this.executeHooks("preUpdate", attributes);
@@ -9437,7 +9649,7 @@ class Resource extends EventEmitter {
9437
9649
  async count({ partition = null, partitionValues = {} } = {}) {
9438
9650
  let prefix;
9439
9651
  if (partition && Object.keys(partitionValues).length > 0) {
9440
- const partitionDef = this.options.partitions[partition];
9652
+ const partitionDef = this.config.partitions[partition];
9441
9653
  if (!partitionDef) {
9442
9654
  throw new Error(`Partition '${partition}' not found`);
9443
9655
  }
@@ -9522,9 +9734,9 @@ class Resource extends EventEmitter {
9522
9734
  return results;
9523
9735
  }
9524
9736
  async deleteAll() {
9525
- if (this.options.paranoid !== false) {
9737
+ if (this.config.paranoid !== false) {
9526
9738
  throw new Error(
9527
- `deleteAll() is a dangerous operation and requires paranoid: false option. Current paranoid setting: ${this.options.paranoid}`
9739
+ `deleteAll() is a dangerous operation and requires paranoid: false option. Current paranoid setting: ${this.config.paranoid}`
9528
9740
  );
9529
9741
  }
9530
9742
  const prefix = `resource=${this.name}/v=${this.version}`;
@@ -9541,9 +9753,9 @@ class Resource extends EventEmitter {
9541
9753
  * @returns {Promise<Object>} Deletion report
9542
9754
  */
9543
9755
  async deleteAllData() {
9544
- if (this.options.paranoid !== false) {
9756
+ if (this.config.paranoid !== false) {
9545
9757
  throw new Error(
9546
- `deleteAllData() is a dangerous operation and requires paranoid: false option. Current paranoid setting: ${this.options.paranoid}`
9758
+ `deleteAllData() is a dangerous operation and requires paranoid: false option. Current paranoid setting: ${this.config.paranoid}`
9547
9759
  );
9548
9760
  }
9549
9761
  const prefix = `resource=${this.name}`;
@@ -9586,10 +9798,10 @@ class Resource extends EventEmitter {
9586
9798
  async listIds({ partition = null, partitionValues = {}, limit, offset = 0 } = {}) {
9587
9799
  let prefix;
9588
9800
  if (partition && Object.keys(partitionValues).length > 0) {
9589
- const partitionDef = this.options.partitions[partition];
9590
- if (!partitionDef) {
9801
+ if (!this.config.partitions || !this.config.partitions[partition]) {
9591
9802
  throw new Error(`Partition '${partition}' not found`);
9592
9803
  }
9804
+ const partitionDef = this.config.partitions[partition];
9593
9805
  const partitionSegments = [];
9594
9806
  const sortedFields = Object.entries(partitionDef.fields).sort(([a], [b]) => a.localeCompare(b));
9595
9807
  for (const [fieldName, rule] of sortedFields) {
@@ -9658,16 +9870,32 @@ class Resource extends EventEmitter {
9658
9870
  if (limit) {
9659
9871
  filteredIds2 = filteredIds2.slice(0, limit);
9660
9872
  }
9661
- const { results: results2 } = await promisePool.PromisePool.for(filteredIds2).withConcurrency(this.parallelism).process(async (id) => {
9662
- return await this.get(id);
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);
9875
+ return null;
9876
+ }).process(async (id) => {
9877
+ try {
9878
+ return await this.get(id);
9879
+ } catch (error) {
9880
+ if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
9881
+ console.warn(`Decryption failed for ${id}, returning basic info`);
9882
+ return {
9883
+ id,
9884
+ _decryptionFailed: true,
9885
+ _error: error.message
9886
+ };
9887
+ }
9888
+ throw error;
9889
+ }
9663
9890
  });
9664
- this.emit("list", { partition, partitionValues, count: results2.length });
9665
- return results2;
9891
+ const validResults2 = results2.filter((item) => item !== null);
9892
+ this.emit("list", { partition, partitionValues, count: validResults2.length, errors: errors2.length });
9893
+ return validResults2;
9666
9894
  }
9667
- const partitionDef = this.options.partitions[partition];
9668
- if (!partitionDef) {
9895
+ if (!this.config.partitions || !this.config.partitions[partition]) {
9669
9896
  throw new Error(`Partition '${partition}' not found`);
9670
9897
  }
9898
+ const partitionDef = this.config.partitions[partition];
9671
9899
  const partitionSegments = [];
9672
9900
  const sortedFields = Object.entries(partitionDef.fields).sort(([a], [b]) => a.localeCompare(b));
9673
9901
  for (const [fieldName, rule] of sortedFields) {
@@ -9693,11 +9921,29 @@ class Resource extends EventEmitter {
9693
9921
  if (limit) {
9694
9922
  filteredIds = filteredIds.slice(0, limit);
9695
9923
  }
9696
- const { results } = await promisePool.PromisePool.for(filteredIds).withConcurrency(this.parallelism).process(async (id) => {
9697
- return await this.getFromPartition({ id, partitionName: partition, partitionValues });
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
+ }
9698
9943
  });
9699
- this.emit("list", { partition, partitionValues, count: results.length });
9700
- return results;
9944
+ const validResults = results.filter((item) => item !== null);
9945
+ this.emit("list", { partition, partitionValues, count: validResults.length, errors: errors.length });
9946
+ return validResults;
9701
9947
  }
9702
9948
  /**
9703
9949
  * Get multiple resources by their IDs
@@ -9708,11 +9954,30 @@ class Resource extends EventEmitter {
9708
9954
  * users.forEach(user => console.log(user.name));
9709
9955
  */
9710
9956
  async getMany(ids) {
9711
- const { results } = await promisePool.PromisePool.for(ids).withConcurrency(this.client.parallelism).process(async (id) => {
9957
+ const { results, errors } = await promisePool.PromisePool.for(ids).withConcurrency(this.client.parallelism).handleError(async (error, id) => {
9958
+ console.warn(`Failed to get resource ${id}:`, error.message);
9959
+ return {
9960
+ id,
9961
+ _error: error.message,
9962
+ _decryptionFailed: error.message.includes("Cipher job failed") || error.message.includes("OperationError")
9963
+ };
9964
+ }).process(async (id) => {
9712
9965
  this.emit("id", id);
9713
- const data = await this.get(id);
9714
- this.emit("data", data);
9715
- return data;
9966
+ try {
9967
+ const data = await this.get(id);
9968
+ this.emit("data", data);
9969
+ return data;
9970
+ } catch (error) {
9971
+ if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
9972
+ console.warn(`Decryption failed for ${id}, returning basic info`);
9973
+ return {
9974
+ id,
9975
+ _decryptionFailed: true,
9976
+ _error: error.message
9977
+ };
9978
+ }
9979
+ throw error;
9980
+ }
9716
9981
  });
9717
9982
  this.emit("getMany", ids.length);
9718
9983
  return results;
@@ -9727,9 +9992,28 @@ class Resource extends EventEmitter {
9727
9992
  async getAll() {
9728
9993
  let ids = await this.listIds();
9729
9994
  if (ids.length === 0) return [];
9730
- const { results } = await promisePool.PromisePool.for(ids).withConcurrency(this.client.parallelism).process(async (id) => {
9731
- const data = await this.get(id);
9732
- return data;
9995
+ const { results, errors } = await promisePool.PromisePool.for(ids).withConcurrency(this.client.parallelism).handleError(async (error, id) => {
9996
+ console.warn(`Failed to get resource ${id}:`, error.message);
9997
+ return {
9998
+ id,
9999
+ _error: error.message,
10000
+ _decryptionFailed: error.message.includes("Cipher job failed") || error.message.includes("OperationError")
10001
+ };
10002
+ }).process(async (id) => {
10003
+ try {
10004
+ const data = await this.get(id);
10005
+ return data;
10006
+ } catch (error) {
10007
+ if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
10008
+ console.warn(`Decryption failed for ${id}, returning basic info`);
10009
+ return {
10010
+ id,
10011
+ _decryptionFailed: true,
10012
+ _error: error.message
10013
+ };
10014
+ }
10015
+ throw error;
10016
+ }
9733
10017
  });
9734
10018
  this.emit("getAll", results.length);
9735
10019
  return results;
@@ -9916,16 +10200,14 @@ class Resource extends EventEmitter {
9916
10200
  }
9917
10201
  /**
9918
10202
  * Generate definition hash for this resource
9919
- * @returns {string} SHA256 hash of the schema definition
10203
+ * @returns {string} SHA256 hash of the resource definition (name + attributes)
9920
10204
  */
9921
10205
  getDefinitionHash() {
9922
- const attributes = this.schema.export().attributes;
9923
- const stableAttributes = { ...attributes };
9924
- if (this.options.timestamps) {
9925
- delete stableAttributes.createdAt;
9926
- delete stableAttributes.updatedAt;
9927
- }
9928
- const stableString = jsonStableStringify(stableAttributes);
10206
+ const definition = {
10207
+ attributes: this.attributes,
10208
+ behavior: this.behavior
10209
+ };
10210
+ const stableString = jsonStableStringify(definition);
9929
10211
  return `sha256:${crypto.createHash("sha256").update(stableString).digest("hex")}`;
9930
10212
  }
9931
10213
  /**
@@ -9944,14 +10226,34 @@ class Resource extends EventEmitter {
9944
10226
  * @returns {Object} Schema object for the version
9945
10227
  */
9946
10228
  async getSchemaForVersion(version) {
9947
- return this.schema;
10229
+ if (version === this.version) {
10230
+ return this.schema;
10231
+ }
10232
+ try {
10233
+ const compatibleSchema = new Schema({
10234
+ name: this.name,
10235
+ attributes: this.attributes,
10236
+ passphrase: this.passphrase,
10237
+ version,
10238
+ options: {
10239
+ ...this.config,
10240
+ // For older versions, be more lenient with decryption
10241
+ autoDecrypt: true,
10242
+ autoEncrypt: true
10243
+ }
10244
+ });
10245
+ return compatibleSchema;
10246
+ } catch (error) {
10247
+ console.warn(`Failed to create compatible schema for version ${version}, using current schema:`, error.message);
10248
+ return this.schema;
10249
+ }
9948
10250
  }
9949
10251
  /**
9950
10252
  * Create partition references after insert
9951
10253
  * @param {Object} data - Inserted object data
9952
10254
  */
9953
10255
  async createPartitionReferences(data) {
9954
- const partitions = this.options.partitions;
10256
+ const partitions = this.config.partitions;
9955
10257
  if (!partitions || Object.keys(partitions).length === 0) {
9956
10258
  return;
9957
10259
  }
@@ -9982,7 +10284,7 @@ class Resource extends EventEmitter {
9982
10284
  * @param {Object} data - Deleted object data
9983
10285
  */
9984
10286
  async deletePartitionReferences(data) {
9985
- const partitions = this.options.partitions;
10287
+ const partitions = this.config.partitions;
9986
10288
  if (!partitions || Object.keys(partitions).length === 0) {
9987
10289
  return;
9988
10290
  }
@@ -10074,7 +10376,7 @@ class Resource extends EventEmitter {
10074
10376
  * @param {Object} data - Updated object data
10075
10377
  */
10076
10378
  async updatePartitionReferences(data) {
10077
- const partitions = this.options.partitions;
10379
+ const partitions = this.config.partitions;
10078
10380
  if (!partitions || Object.keys(partitions).length === 0) {
10079
10381
  return;
10080
10382
  }
@@ -10130,10 +10432,10 @@ class Resource extends EventEmitter {
10130
10432
  * });
10131
10433
  */
10132
10434
  async getFromPartition({ id, partitionName, partitionValues = {} }) {
10133
- const partition = this.options.partitions[partitionName];
10134
- if (!partition) {
10435
+ if (!this.config.partitions || !this.config.partitions[partitionName]) {
10135
10436
  throw new Error(`Partition '${partitionName}' not found`);
10136
10437
  }
10438
+ const partition = this.config.partitions[partitionName];
10137
10439
  const partitionSegments = [];
10138
10440
  const sortedFields = Object.entries(partition.fields).sort(([a], [b]) => a.localeCompare(b));
10139
10441
  for (const [fieldName, rule] of sortedFields) {
@@ -10181,6 +10483,98 @@ class Resource extends EventEmitter {
10181
10483
  return data;
10182
10484
  }
10183
10485
  }
10486
+ function validateResourceConfig(config) {
10487
+ const errors = [];
10488
+ if (!config.name) {
10489
+ errors.push("Resource 'name' is required");
10490
+ } else if (typeof config.name !== "string") {
10491
+ errors.push("Resource 'name' must be a string");
10492
+ } else if (config.name.trim() === "") {
10493
+ errors.push("Resource 'name' cannot be empty");
10494
+ }
10495
+ if (!config.client) {
10496
+ errors.push("S3 'client' is required");
10497
+ }
10498
+ if (!config.attributes) {
10499
+ errors.push("Resource 'attributes' are required");
10500
+ } else if (typeof config.attributes !== "object" || Array.isArray(config.attributes)) {
10501
+ errors.push("Resource 'attributes' must be an object");
10502
+ } else if (Object.keys(config.attributes).length === 0) {
10503
+ errors.push("Resource 'attributes' cannot be empty");
10504
+ }
10505
+ if (config.version !== void 0 && typeof config.version !== "string") {
10506
+ errors.push("Resource 'version' must be a string");
10507
+ }
10508
+ if (config.behavior !== void 0 && typeof config.behavior !== "string") {
10509
+ errors.push("Resource 'behavior' must be a string");
10510
+ }
10511
+ if (config.passphrase !== void 0 && typeof config.passphrase !== "string") {
10512
+ errors.push("Resource 'passphrase' must be a string");
10513
+ }
10514
+ if (config.parallelism !== void 0) {
10515
+ if (typeof config.parallelism !== "number" || !Number.isInteger(config.parallelism)) {
10516
+ errors.push("Resource 'parallelism' must be an integer");
10517
+ } else if (config.parallelism < 1) {
10518
+ errors.push("Resource 'parallelism' must be greater than 0");
10519
+ }
10520
+ }
10521
+ if (config.observers !== void 0 && !Array.isArray(config.observers)) {
10522
+ errors.push("Resource 'observers' must be an array");
10523
+ }
10524
+ const booleanFields = ["cache", "autoDecrypt", "timestamps", "paranoid", "allNestedObjectsOptional"];
10525
+ for (const field of booleanFields) {
10526
+ if (config[field] !== void 0 && typeof config[field] !== "boolean") {
10527
+ errors.push(`Resource '${field}' must be a boolean`);
10528
+ }
10529
+ }
10530
+ if (config.partitions !== void 0) {
10531
+ if (typeof config.partitions !== "object" || Array.isArray(config.partitions)) {
10532
+ errors.push("Resource 'partitions' must be an object");
10533
+ } else {
10534
+ for (const [partitionName, partitionDef] of Object.entries(config.partitions)) {
10535
+ if (typeof partitionDef !== "object" || Array.isArray(partitionDef)) {
10536
+ errors.push(`Partition '${partitionName}' must be an object`);
10537
+ } else if (!partitionDef.fields) {
10538
+ errors.push(`Partition '${partitionName}' must have a 'fields' property`);
10539
+ } else if (typeof partitionDef.fields !== "object" || Array.isArray(partitionDef.fields)) {
10540
+ errors.push(`Partition '${partitionName}.fields' must be an object`);
10541
+ } else {
10542
+ for (const [fieldName, fieldType] of Object.entries(partitionDef.fields)) {
10543
+ if (typeof fieldType !== "string") {
10544
+ errors.push(`Partition '${partitionName}.fields.${fieldName}' must be a string`);
10545
+ }
10546
+ }
10547
+ }
10548
+ }
10549
+ }
10550
+ }
10551
+ if (config.hooks !== void 0) {
10552
+ if (typeof config.hooks !== "object" || Array.isArray(config.hooks)) {
10553
+ errors.push("Resource 'hooks' must be an object");
10554
+ } else {
10555
+ const validHookEvents = ["preInsert", "afterInsert", "preUpdate", "afterUpdate", "preDelete", "afterDelete"];
10556
+ for (const [event, hooksArr] of Object.entries(config.hooks)) {
10557
+ if (!validHookEvents.includes(event)) {
10558
+ errors.push(`Invalid hook event '${event}'. Valid events: ${validHookEvents.join(", ")}`);
10559
+ } else if (!Array.isArray(hooksArr)) {
10560
+ errors.push(`Resource 'hooks.${event}' must be an array`);
10561
+ } else {
10562
+ for (let i = 0; i < hooksArr.length; i++) {
10563
+ const hook = hooksArr[i];
10564
+ if (typeof hook !== "function") {
10565
+ if (typeof hook === "string") continue;
10566
+ continue;
10567
+ }
10568
+ }
10569
+ }
10570
+ }
10571
+ }
10572
+ }
10573
+ return {
10574
+ isValid: errors.length === 0,
10575
+ errors
10576
+ };
10577
+ }
10184
10578
 
10185
10579
  class Database extends EventEmitter {
10186
10580
  constructor(options) {
@@ -10188,7 +10582,7 @@ class Database extends EventEmitter {
10188
10582
  this.version = "1";
10189
10583
  this.s3dbVersion = (() => {
10190
10584
  try {
10191
- return true ? "4.1.7" : "latest";
10585
+ return true ? "4.1.10" : "latest";
10192
10586
  } catch (e) {
10193
10587
  return "latest";
10194
10588
  }
@@ -10201,10 +10595,21 @@ class Database extends EventEmitter {
10201
10595
  this.plugins = options.plugins || [];
10202
10596
  this.cache = options.cache;
10203
10597
  this.passphrase = options.passphrase || "secret";
10598
+ let connectionString = options.connectionString;
10599
+ 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
+ });
10608
+ }
10204
10609
  this.client = options.client || new Client({
10205
10610
  verbose: this.verbose,
10206
10611
  parallelism: this.parallelism,
10207
- connectionString: options.connectionString
10612
+ connectionString
10208
10613
  });
10209
10614
  this.bucket = this.client.bucket;
10210
10615
  this.keyPrefix = this.client.keyPrefix;
@@ -10229,15 +10634,18 @@ class Database extends EventEmitter {
10229
10634
  name,
10230
10635
  client: this.client,
10231
10636
  version: currentVersion,
10232
- options: {
10233
- ...versionData.options,
10234
- partitions: resourceMetadata.partitions || versionData.options?.partitions || {}
10235
- },
10236
10637
  attributes: versionData.attributes,
10237
10638
  behavior: versionData.behavior || "user-management",
10238
10639
  parallelism: this.parallelism,
10239
10640
  passphrase: this.passphrase,
10240
- observers: [this]
10641
+ observers: [this],
10642
+ 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: {}
10241
10649
  });
10242
10650
  }
10243
10651
  }
@@ -10306,7 +10714,7 @@ class Database extends EventEmitter {
10306
10714
  generateDefinitionHash(definition, behavior = void 0) {
10307
10715
  const attributes = definition.attributes;
10308
10716
  const stableAttributes = { ...attributes };
10309
- if (definition.options?.timestamps) {
10717
+ if (definition.timestamps) {
10310
10718
  delete stableAttributes.createdAt;
10311
10719
  delete stableAttributes.updatedAt;
10312
10720
  }
@@ -10368,14 +10776,21 @@ class Database extends EventEmitter {
10368
10776
  }
10369
10777
  metadata.resources[name] = {
10370
10778
  currentVersion: version,
10371
- partitions: resourceDef.options?.partitions || {},
10779
+ partitions: resource.config.partitions || {},
10372
10780
  versions: {
10373
10781
  ...existingResource?.versions,
10374
10782
  // Preserve previous versions
10375
10783
  [version]: {
10376
10784
  hash: definitionHash,
10377
10785
  attributes: resourceDef.attributes,
10378
- options: resourceDef.options,
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
+ },
10379
10794
  behavior: resourceDef.behavior || "user-management",
10380
10795
  createdAt: isNewVersion ? (/* @__PURE__ */ new Date()).toISOString() : existingVersionData?.createdAt
10381
10796
  }
@@ -10428,10 +10843,9 @@ class Database extends EventEmitter {
10428
10843
  observers: [],
10429
10844
  client: this.client,
10430
10845
  version: "temp",
10431
- options: {
10432
- cache: this.cache,
10433
- ...options
10434
- }
10846
+ passphrase: this.passphrase,
10847
+ cache: this.cache,
10848
+ ...options
10435
10849
  });
10436
10850
  const newHash = this.generateDefinitionHash(tempResource.export(), behavior);
10437
10851
  const existingHash = this.generateDefinitionHash(this.resources[name].export(), this.resources[name].behavior);
@@ -10471,7 +10885,7 @@ class Database extends EventEmitter {
10471
10885
  async createResource({ name, attributes, options = {}, behavior = "user-management" }) {
10472
10886
  if (this.resources[name]) {
10473
10887
  const existingResource = this.resources[name];
10474
- Object.assign(existingResource.options, {
10888
+ Object.assign(existingResource.config, {
10475
10889
  cache: this.cache,
10476
10890
  ...options
10477
10891
  });
@@ -10498,10 +10912,9 @@ class Database extends EventEmitter {
10498
10912
  observers: [this],
10499
10913
  client: this.client,
10500
10914
  version,
10501
- options: {
10502
- cache: this.cache,
10503
- ...options
10504
- }
10915
+ passphrase: this.passphrase,
10916
+ cache: this.cache,
10917
+ ...options
10505
10918
  });
10506
10919
  this.resources[name] = resource;
10507
10920
  await this.uploadMetadataFile();
@@ -17203,6 +17616,7 @@ class CachePlugin extends Plugin {
17203
17616
  }
17204
17617
  }
17205
17618
 
17619
+ exports.AuthenticationError = AuthenticationError;
17206
17620
  exports.BaseError = BaseError;
17207
17621
  exports.Cache = Cache;
17208
17622
  exports.CachePlugin = CachePlugin;
@@ -17210,6 +17624,8 @@ exports.Client = Client;
17210
17624
  exports.ConnectionString = ConnectionString;
17211
17625
  exports.CostsPlugin = CostsPlugin;
17212
17626
  exports.Database = Database;
17627
+ exports.DatabaseError = DatabaseError;
17628
+ exports.EncryptionError = EncryptionError;
17213
17629
  exports.ErrorMap = ErrorMap;
17214
17630
  exports.InvalidResourceItem = InvalidResourceItem;
17215
17631
  exports.MemoryCache = MemoryCache;
@@ -17217,20 +17633,28 @@ exports.MissingMetadata = MissingMetadata;
17217
17633
  exports.NoSuchBucket = NoSuchBucket;
17218
17634
  exports.NoSuchKey = NoSuchKey;
17219
17635
  exports.NotFound = NotFound;
17636
+ exports.PermissionError = PermissionError;
17220
17637
  exports.Plugin = Plugin;
17221
17638
  exports.PluginObject = PluginObject;
17222
17639
  exports.ResourceIdsPageReader = ResourceIdsPageReader;
17223
17640
  exports.ResourceIdsReader = ResourceIdsReader;
17641
+ exports.ResourceNotFound = ResourceNotFound;
17642
+ exports.ResourceNotFoundError = ResourceNotFound;
17224
17643
  exports.ResourceReader = ResourceReader;
17225
17644
  exports.ResourceWriter = ResourceWriter;
17226
17645
  exports.S3Cache = S3Cache;
17646
+ exports.S3DB = S3db;
17647
+ exports.S3DBError = S3DBError;
17227
17648
  exports.S3_DEFAULT_ENDPOINT = S3_DEFAULT_ENDPOINT;
17228
17649
  exports.S3_DEFAULT_REGION = S3_DEFAULT_REGION;
17229
17650
  exports.S3db = S3db;
17651
+ exports.S3dbError = S3DBError;
17230
17652
  exports.UnknownError = UnknownError;
17653
+ exports.ValidationError = ValidationError;
17231
17654
  exports.Validator = Validator;
17232
17655
  exports.ValidatorManager = ValidatorManager;
17233
17656
  exports.decrypt = decrypt;
17657
+ exports.default = S3db;
17234
17658
  exports.encrypt = encrypt;
17235
17659
  exports.sha256 = sha256;
17236
17660
  exports.streamToString = streamToString;