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/README.md +786 -1706
- package/dist/s3db.cjs.js +549 -125
- package/dist/s3db.cjs.min.js +14 -12
- package/dist/s3db.es.js +537 -126
- package/dist/s3db.es.min.js +12 -10
- package/dist/s3db.iife.js +549 -125
- package/dist/s3db.iife.min.js +14 -12
- package/package.json +1 -1
package/dist/s3db.es.js
CHANGED
|
@@ -242,6 +242,8 @@ var substr = 'ab'.substr(-1) === 'b' ?
|
|
|
242
242
|
;
|
|
243
243
|
|
|
244
244
|
const idGenerator = customAlphabet(urlAlphabet, 22);
|
|
245
|
+
const passwordAlphabet = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789";
|
|
246
|
+
customAlphabet(passwordAlphabet, 12);
|
|
245
247
|
|
|
246
248
|
var domain;
|
|
247
249
|
|
|
@@ -742,42 +744,85 @@ ${JSON.stringify(rest, null, 2)}`;
|
|
|
742
744
|
return `${this.name} | ${this.message}`;
|
|
743
745
|
}
|
|
744
746
|
}
|
|
745
|
-
class
|
|
747
|
+
class S3DBError extends BaseError {
|
|
748
|
+
constructor(message, details = {}) {
|
|
749
|
+
super({ message, ...details });
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
class DatabaseError extends S3DBError {
|
|
753
|
+
constructor(message, details = {}) {
|
|
754
|
+
super(message, details);
|
|
755
|
+
Object.assign(this, details);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
class ValidationError extends S3DBError {
|
|
759
|
+
constructor(message, details = {}) {
|
|
760
|
+
super(message, details);
|
|
761
|
+
Object.assign(this, details);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
class AuthenticationError extends S3DBError {
|
|
765
|
+
constructor(message, details = {}) {
|
|
766
|
+
super(message, details);
|
|
767
|
+
Object.assign(this, details);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
class PermissionError extends S3DBError {
|
|
771
|
+
constructor(message, details = {}) {
|
|
772
|
+
super(message, details);
|
|
773
|
+
Object.assign(this, details);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
class EncryptionError extends S3DBError {
|
|
777
|
+
constructor(message, details = {}) {
|
|
778
|
+
super(message, details);
|
|
779
|
+
Object.assign(this, details);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
class ResourceNotFound extends S3DBError {
|
|
783
|
+
constructor({ bucket, resourceName, id, ...rest }) {
|
|
784
|
+
super(`Resource not found: ${resourceName}/${id} [bucket:${bucket}]`, {
|
|
785
|
+
bucket,
|
|
786
|
+
resourceName,
|
|
787
|
+
id,
|
|
788
|
+
...rest
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
class NoSuchBucket extends S3DBError {
|
|
746
793
|
constructor({ bucket, ...rest }) {
|
|
747
|
-
super(
|
|
794
|
+
super(`Bucket does not exists [bucket:${bucket}]`, { bucket, ...rest });
|
|
748
795
|
}
|
|
749
796
|
}
|
|
750
|
-
class NoSuchKey extends
|
|
797
|
+
class NoSuchKey extends S3DBError {
|
|
751
798
|
constructor({ bucket, key, ...rest }) {
|
|
752
|
-
super(
|
|
753
|
-
this.key = key;
|
|
799
|
+
super(`Key [${key}] does not exists [bucket:${bucket}/${key}]`, { bucket, key, ...rest });
|
|
754
800
|
}
|
|
755
801
|
}
|
|
756
802
|
class NotFound extends NoSuchKey {
|
|
757
803
|
}
|
|
758
|
-
class MissingMetadata extends
|
|
804
|
+
class MissingMetadata extends S3DBError {
|
|
759
805
|
constructor({ bucket, ...rest }) {
|
|
760
|
-
super(
|
|
806
|
+
super(`Missing metadata for bucket [bucket:${bucket}]`, { bucket, ...rest });
|
|
761
807
|
}
|
|
762
808
|
}
|
|
763
|
-
class InvalidResourceItem extends
|
|
809
|
+
class InvalidResourceItem extends S3DBError {
|
|
764
810
|
constructor({
|
|
765
811
|
bucket,
|
|
766
812
|
resourceName,
|
|
767
813
|
attributes,
|
|
768
814
|
validation
|
|
769
815
|
}) {
|
|
770
|
-
super({
|
|
816
|
+
super(`This item is not valid. Resource=${resourceName} [bucket:${bucket}].
|
|
817
|
+
${JSON.stringify(validation, null, 2)}`, {
|
|
771
818
|
bucket,
|
|
772
|
-
|
|
773
|
-
|
|
819
|
+
resourceName,
|
|
820
|
+
attributes,
|
|
821
|
+
validation
|
|
774
822
|
});
|
|
775
|
-
this.resourceName = resourceName;
|
|
776
|
-
this.attributes = attributes;
|
|
777
|
-
this.validation = validation;
|
|
778
823
|
}
|
|
779
824
|
}
|
|
780
|
-
class UnknownError extends
|
|
825
|
+
class UnknownError extends S3DBError {
|
|
781
826
|
}
|
|
782
827
|
const ErrorMap = {
|
|
783
828
|
"NotFound": NotFound,
|
|
@@ -806,9 +851,9 @@ class ConnectionString {
|
|
|
806
851
|
}
|
|
807
852
|
}
|
|
808
853
|
defineS3(uri) {
|
|
809
|
-
this.bucket = uri.hostname;
|
|
810
|
-
this.accessKeyId = uri.username;
|
|
811
|
-
this.secretAccessKey = uri.password;
|
|
854
|
+
this.bucket = decodeURIComponent(uri.hostname);
|
|
855
|
+
this.accessKeyId = decodeURIComponent(uri.username);
|
|
856
|
+
this.secretAccessKey = decodeURIComponent(uri.password);
|
|
812
857
|
this.endpoint = S3_DEFAULT_ENDPOINT;
|
|
813
858
|
if (["/", "", null].includes(uri.pathname)) {
|
|
814
859
|
this.keyPrefix = "";
|
|
@@ -820,14 +865,14 @@ class ConnectionString {
|
|
|
820
865
|
defineMinio(uri) {
|
|
821
866
|
this.forcePathStyle = true;
|
|
822
867
|
this.endpoint = uri.origin;
|
|
823
|
-
this.accessKeyId = uri.username;
|
|
824
|
-
this.secretAccessKey = uri.password;
|
|
868
|
+
this.accessKeyId = decodeURIComponent(uri.username);
|
|
869
|
+
this.secretAccessKey = decodeURIComponent(uri.password);
|
|
825
870
|
if (["/", "", null].includes(uri.pathname)) {
|
|
826
871
|
this.bucket = "s3db";
|
|
827
872
|
this.keyPrefix = "";
|
|
828
873
|
} else {
|
|
829
874
|
let [, bucket, ...subpath] = uri.pathname.split("/");
|
|
830
|
-
this.bucket = bucket;
|
|
875
|
+
this.bucket = decodeURIComponent(bucket);
|
|
831
876
|
this.keyPrefix = [...subpath || []].join("/");
|
|
832
877
|
}
|
|
833
878
|
}
|
|
@@ -8836,18 +8881,83 @@ function getBehavior(behaviorName) {
|
|
|
8836
8881
|
const DEFAULT_BEHAVIOR = "user-management";
|
|
8837
8882
|
|
|
8838
8883
|
class Resource extends EventEmitter {
|
|
8839
|
-
|
|
8840
|
-
|
|
8841
|
-
|
|
8842
|
-
|
|
8843
|
-
|
|
8844
|
-
|
|
8845
|
-
|
|
8846
|
-
|
|
8847
|
-
|
|
8848
|
-
|
|
8849
|
-
|
|
8884
|
+
/**
|
|
8885
|
+
* Create a new Resource instance
|
|
8886
|
+
* @param {Object} config - Resource configuration
|
|
8887
|
+
* @param {string} config.name - Resource name
|
|
8888
|
+
* @param {Object} config.client - S3 client instance
|
|
8889
|
+
* @param {string} [config.version='v0'] - Resource version
|
|
8890
|
+
* @param {Object} [config.attributes={}] - Resource attributes schema
|
|
8891
|
+
* @param {string} [config.behavior='user-management'] - Resource behavior strategy
|
|
8892
|
+
* @param {string} [config.passphrase='secret'] - Encryption passphrase
|
|
8893
|
+
* @param {number} [config.parallelism=10] - Parallelism for bulk operations
|
|
8894
|
+
* @param {Array} [config.observers=[]] - Observer instances
|
|
8895
|
+
* @param {boolean} [config.cache=false] - Enable caching
|
|
8896
|
+
* @param {boolean} [config.autoDecrypt=true] - Auto-decrypt secret fields
|
|
8897
|
+
* @param {boolean} [config.timestamps=false] - Enable automatic timestamps
|
|
8898
|
+
* @param {Object} [config.partitions={}] - Partition definitions
|
|
8899
|
+
* @param {boolean} [config.paranoid=true] - Security flag for dangerous operations
|
|
8900
|
+
* @param {boolean} [config.allNestedObjectsOptional=false] - Make nested objects optional
|
|
8901
|
+
* @param {Object} [config.hooks={}] - Custom hooks
|
|
8902
|
+
* @param {Object} [config.options={}] - Additional options
|
|
8903
|
+
* @example
|
|
8904
|
+
* const users = new Resource({
|
|
8905
|
+
* name: 'users',
|
|
8906
|
+
* client: s3Client,
|
|
8907
|
+
* attributes: {
|
|
8908
|
+
* name: 'string|required',
|
|
8909
|
+
* email: 'string|required',
|
|
8910
|
+
* password: 'secret|required'
|
|
8911
|
+
* },
|
|
8912
|
+
* behavior: 'user-management',
|
|
8913
|
+
* passphrase: 'my-secret-key',
|
|
8914
|
+
* timestamps: true,
|
|
8915
|
+
* partitions: {
|
|
8916
|
+
* byRegion: {
|
|
8917
|
+
* fields: { region: 'string' }
|
|
8918
|
+
* }
|
|
8919
|
+
* },
|
|
8920
|
+
* hooks: {
|
|
8921
|
+
* preInsert: [async (data) => {
|
|
8922
|
+
* console.log('Pre-insert hook:', data);
|
|
8923
|
+
* return data;
|
|
8924
|
+
* }]
|
|
8925
|
+
* }
|
|
8926
|
+
* });
|
|
8927
|
+
*/
|
|
8928
|
+
constructor(config) {
|
|
8850
8929
|
super();
|
|
8930
|
+
const validation = validateResourceConfig(config);
|
|
8931
|
+
if (!validation.isValid) {
|
|
8932
|
+
throw new Error(`Invalid Resource configuration:
|
|
8933
|
+
${validation.errors.join("\n")}`);
|
|
8934
|
+
}
|
|
8935
|
+
const {
|
|
8936
|
+
name,
|
|
8937
|
+
client,
|
|
8938
|
+
version = "1",
|
|
8939
|
+
attributes = {},
|
|
8940
|
+
behavior = DEFAULT_BEHAVIOR,
|
|
8941
|
+
passphrase = "secret",
|
|
8942
|
+
parallelism = 10,
|
|
8943
|
+
observers = [],
|
|
8944
|
+
cache = false,
|
|
8945
|
+
autoDecrypt = true,
|
|
8946
|
+
timestamps = false,
|
|
8947
|
+
partitions = {},
|
|
8948
|
+
paranoid = true,
|
|
8949
|
+
allNestedObjectsOptional = true,
|
|
8950
|
+
hooks = {},
|
|
8951
|
+
options = {}
|
|
8952
|
+
} = config;
|
|
8953
|
+
const mergedOptions = {
|
|
8954
|
+
cache: typeof options.cache === "boolean" ? options.cache : cache,
|
|
8955
|
+
autoDecrypt: typeof options.autoDecrypt === "boolean" ? options.autoDecrypt : autoDecrypt,
|
|
8956
|
+
timestamps: typeof options.timestamps === "boolean" ? options.timestamps : timestamps,
|
|
8957
|
+
paranoid: typeof options.paranoid === "boolean" ? options.paranoid : paranoid,
|
|
8958
|
+
allNestedObjectsOptional: typeof options.allNestedObjectsOptional === "boolean" ? options.allNestedObjectsOptional : allNestedObjectsOptional,
|
|
8959
|
+
partitions: options.partitions || partitions || {}
|
|
8960
|
+
};
|
|
8851
8961
|
this.name = name;
|
|
8852
8962
|
this.client = client;
|
|
8853
8963
|
this.version = version;
|
|
@@ -8855,15 +8965,14 @@ class Resource extends EventEmitter {
|
|
|
8855
8965
|
this.observers = observers;
|
|
8856
8966
|
this.parallelism = parallelism;
|
|
8857
8967
|
this.passphrase = passphrase ?? "secret";
|
|
8858
|
-
this.
|
|
8859
|
-
cache:
|
|
8860
|
-
|
|
8861
|
-
|
|
8862
|
-
|
|
8863
|
-
|
|
8864
|
-
|
|
8865
|
-
allNestedObjectsOptional:
|
|
8866
|
-
...options
|
|
8968
|
+
this.config = {
|
|
8969
|
+
cache: mergedOptions.cache,
|
|
8970
|
+
hooks,
|
|
8971
|
+
paranoid: mergedOptions.paranoid,
|
|
8972
|
+
timestamps: mergedOptions.timestamps,
|
|
8973
|
+
partitions: mergedOptions.partitions,
|
|
8974
|
+
autoDecrypt: mergedOptions.autoDecrypt,
|
|
8975
|
+
allNestedObjectsOptional: mergedOptions.allNestedObjectsOptional
|
|
8867
8976
|
};
|
|
8868
8977
|
this.hooks = {
|
|
8869
8978
|
preInsert: [],
|
|
@@ -8874,18 +8983,21 @@ class Resource extends EventEmitter {
|
|
|
8874
8983
|
afterDelete: []
|
|
8875
8984
|
};
|
|
8876
8985
|
this.attributes = attributes || {};
|
|
8877
|
-
if (
|
|
8986
|
+
if (this.config.timestamps) {
|
|
8878
8987
|
this.attributes.createdAt = "string|optional";
|
|
8879
8988
|
this.attributes.updatedAt = "string|optional";
|
|
8880
|
-
if (!this.
|
|
8881
|
-
this.
|
|
8989
|
+
if (!this.config.partitions) {
|
|
8990
|
+
this.config.partitions = {};
|
|
8991
|
+
}
|
|
8992
|
+
if (!this.config.partitions.byCreatedDate) {
|
|
8993
|
+
this.config.partitions.byCreatedDate = {
|
|
8882
8994
|
fields: {
|
|
8883
8995
|
createdAt: "date|maxlength:10"
|
|
8884
8996
|
}
|
|
8885
8997
|
};
|
|
8886
8998
|
}
|
|
8887
|
-
if (!this.
|
|
8888
|
-
this.
|
|
8999
|
+
if (!this.config.partitions.byUpdatedDate) {
|
|
9000
|
+
this.config.partitions.byUpdatedDate = {
|
|
8889
9001
|
fields: {
|
|
8890
9002
|
updatedAt: "date|maxlength:10"
|
|
8891
9003
|
}
|
|
@@ -8898,25 +9010,47 @@ class Resource extends EventEmitter {
|
|
|
8898
9010
|
passphrase,
|
|
8899
9011
|
version: this.version,
|
|
8900
9012
|
options: {
|
|
8901
|
-
|
|
8902
|
-
allNestedObjectsOptional: this.
|
|
9013
|
+
autoDecrypt: this.config.autoDecrypt,
|
|
9014
|
+
allNestedObjectsOptional: this.config.allNestedObjectsOptional
|
|
8903
9015
|
}
|
|
8904
9016
|
});
|
|
8905
|
-
this.validatePartitions();
|
|
8906
9017
|
this.setupPartitionHooks();
|
|
8907
|
-
|
|
8908
|
-
|
|
9018
|
+
this.validatePartitions();
|
|
9019
|
+
if (hooks) {
|
|
9020
|
+
for (const [event, hooksArr] of Object.entries(hooks)) {
|
|
8909
9021
|
if (Array.isArray(hooksArr) && this.hooks[event]) {
|
|
8910
9022
|
for (const fn of hooksArr) {
|
|
8911
|
-
|
|
9023
|
+
if (typeof fn === "function") {
|
|
9024
|
+
this.hooks[event].push(fn.bind(this));
|
|
9025
|
+
}
|
|
8912
9026
|
}
|
|
8913
9027
|
}
|
|
8914
9028
|
}
|
|
8915
9029
|
}
|
|
8916
9030
|
}
|
|
9031
|
+
/**
|
|
9032
|
+
* Get resource options (for backward compatibility with tests)
|
|
9033
|
+
*/
|
|
9034
|
+
get options() {
|
|
9035
|
+
return {
|
|
9036
|
+
timestamps: this.config.timestamps,
|
|
9037
|
+
partitions: this.config.partitions || {},
|
|
9038
|
+
cache: this.config.cache,
|
|
9039
|
+
autoDecrypt: this.config.autoDecrypt,
|
|
9040
|
+
paranoid: this.config.paranoid,
|
|
9041
|
+
allNestedObjectsOptional: this.config.allNestedObjectsOptional
|
|
9042
|
+
};
|
|
9043
|
+
}
|
|
8917
9044
|
export() {
|
|
8918
9045
|
const exported = this.schema.export();
|
|
8919
9046
|
exported.behavior = this.behavior;
|
|
9047
|
+
exported.timestamps = this.config.timestamps;
|
|
9048
|
+
exported.partitions = this.config.partitions || {};
|
|
9049
|
+
exported.paranoid = this.config.paranoid;
|
|
9050
|
+
exported.allNestedObjectsOptional = this.config.allNestedObjectsOptional;
|
|
9051
|
+
exported.autoDecrypt = this.config.autoDecrypt;
|
|
9052
|
+
exported.cache = this.config.cache;
|
|
9053
|
+
exported.hooks = this.hooks;
|
|
8920
9054
|
return exported;
|
|
8921
9055
|
}
|
|
8922
9056
|
/**
|
|
@@ -8926,18 +9060,21 @@ class Resource extends EventEmitter {
|
|
|
8926
9060
|
updateAttributes(newAttributes) {
|
|
8927
9061
|
const oldAttributes = this.attributes;
|
|
8928
9062
|
this.attributes = newAttributes;
|
|
8929
|
-
if (this.
|
|
9063
|
+
if (this.config.timestamps) {
|
|
8930
9064
|
newAttributes.createdAt = "string|optional";
|
|
8931
9065
|
newAttributes.updatedAt = "string|optional";
|
|
8932
|
-
if (!this.
|
|
8933
|
-
this.
|
|
9066
|
+
if (!this.config.partitions) {
|
|
9067
|
+
this.config.partitions = {};
|
|
9068
|
+
}
|
|
9069
|
+
if (!this.config.partitions.byCreatedDate) {
|
|
9070
|
+
this.config.partitions.byCreatedDate = {
|
|
8934
9071
|
fields: {
|
|
8935
9072
|
createdAt: "date|maxlength:10"
|
|
8936
9073
|
}
|
|
8937
9074
|
};
|
|
8938
9075
|
}
|
|
8939
|
-
if (!this.
|
|
8940
|
-
this.
|
|
9076
|
+
if (!this.config.partitions.byUpdatedDate) {
|
|
9077
|
+
this.config.partitions.byUpdatedDate = {
|
|
8941
9078
|
fields: {
|
|
8942
9079
|
updatedAt: "date|maxlength:10"
|
|
8943
9080
|
}
|
|
@@ -8949,10 +9086,13 @@ class Resource extends EventEmitter {
|
|
|
8949
9086
|
attributes: newAttributes,
|
|
8950
9087
|
passphrase: this.passphrase,
|
|
8951
9088
|
version: this.version,
|
|
8952
|
-
options:
|
|
9089
|
+
options: {
|
|
9090
|
+
autoDecrypt: this.config.autoDecrypt,
|
|
9091
|
+
allNestedObjectsOptional: this.config.allNestedObjectsOptional
|
|
9092
|
+
}
|
|
8953
9093
|
});
|
|
8954
|
-
this.validatePartitions();
|
|
8955
9094
|
this.setupPartitionHooks();
|
|
9095
|
+
this.validatePartitions();
|
|
8956
9096
|
return { oldAttributes, newAttributes };
|
|
8957
9097
|
}
|
|
8958
9098
|
/**
|
|
@@ -8983,15 +9123,24 @@ class Resource extends EventEmitter {
|
|
|
8983
9123
|
* Setup automatic partition hooks
|
|
8984
9124
|
*/
|
|
8985
9125
|
setupPartitionHooks() {
|
|
8986
|
-
|
|
8987
|
-
|
|
9126
|
+
if (!this.config.partitions) {
|
|
9127
|
+
return;
|
|
9128
|
+
}
|
|
9129
|
+
const partitions = this.config.partitions;
|
|
9130
|
+
if (Object.keys(partitions).length === 0) {
|
|
8988
9131
|
return;
|
|
8989
9132
|
}
|
|
8990
|
-
this.
|
|
9133
|
+
if (!this.hooks.afterInsert) {
|
|
9134
|
+
this.hooks.afterInsert = [];
|
|
9135
|
+
}
|
|
9136
|
+
this.hooks.afterInsert.push(async (data) => {
|
|
8991
9137
|
await this.createPartitionReferences(data);
|
|
8992
9138
|
return data;
|
|
8993
9139
|
});
|
|
8994
|
-
this.
|
|
9140
|
+
if (!this.hooks.afterDelete) {
|
|
9141
|
+
this.hooks.afterDelete = [];
|
|
9142
|
+
}
|
|
9143
|
+
this.hooks.afterDelete.push(async (data) => {
|
|
8995
9144
|
await this.deletePartitionReferences(data);
|
|
8996
9145
|
return data;
|
|
8997
9146
|
});
|
|
@@ -9016,8 +9165,11 @@ class Resource extends EventEmitter {
|
|
|
9016
9165
|
* @throws {Error} If partition fields don't exist in current schema
|
|
9017
9166
|
*/
|
|
9018
9167
|
validatePartitions() {
|
|
9019
|
-
|
|
9020
|
-
|
|
9168
|
+
if (!this.config.partitions) {
|
|
9169
|
+
return;
|
|
9170
|
+
}
|
|
9171
|
+
const partitions = this.config.partitions;
|
|
9172
|
+
if (Object.keys(partitions).length === 0) {
|
|
9021
9173
|
return;
|
|
9022
9174
|
}
|
|
9023
9175
|
const currentAttributes = Object.keys(this.attributes || {});
|
|
@@ -9124,10 +9276,10 @@ class Resource extends EventEmitter {
|
|
|
9124
9276
|
* // Returns: null
|
|
9125
9277
|
*/
|
|
9126
9278
|
getPartitionKey({ partitionName, id, data }) {
|
|
9127
|
-
|
|
9128
|
-
if (!partition) {
|
|
9279
|
+
if (!this.config.partitions || !this.config.partitions[partitionName]) {
|
|
9129
9280
|
throw new Error(`Partition '${partitionName}' not found`);
|
|
9130
9281
|
}
|
|
9282
|
+
const partition = this.config.partitions[partitionName];
|
|
9131
9283
|
const partitionSegments = [];
|
|
9132
9284
|
const sortedFields = Object.entries(partition.fields).sort(([a], [b]) => a.localeCompare(b));
|
|
9133
9285
|
for (const [fieldName, rule] of sortedFields) {
|
|
@@ -9183,6 +9335,12 @@ class Resource extends EventEmitter {
|
|
|
9183
9335
|
* name: 'Jane Smith',
|
|
9184
9336
|
* email: 'jane@example.com'
|
|
9185
9337
|
* });
|
|
9338
|
+
*
|
|
9339
|
+
* // Insert with auto-generated password for secret field
|
|
9340
|
+
* const user = await resource.insert({
|
|
9341
|
+
* name: 'John Doe',
|
|
9342
|
+
* email: 'john@example.com',
|
|
9343
|
+
* });
|
|
9186
9344
|
*/
|
|
9187
9345
|
async insert({ id, ...attributes }) {
|
|
9188
9346
|
if (this.options.timestamps) {
|
|
@@ -9267,6 +9425,58 @@ class Resource extends EventEmitter {
|
|
|
9267
9425
|
this.emit("get", data);
|
|
9268
9426
|
return data;
|
|
9269
9427
|
} catch (error) {
|
|
9428
|
+
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError") || error.originalError?.message?.includes("Cipher job failed")) {
|
|
9429
|
+
try {
|
|
9430
|
+
console.warn(`Decryption failed for resource ${id}, attempting to get raw metadata`);
|
|
9431
|
+
const request = await this.client.headObject(key);
|
|
9432
|
+
const objectVersion = this.extractVersionFromKey(key) || this.version;
|
|
9433
|
+
const tempSchema = new Schema({
|
|
9434
|
+
name: this.name,
|
|
9435
|
+
attributes: this.attributes,
|
|
9436
|
+
passphrase: this.passphrase,
|
|
9437
|
+
version: objectVersion,
|
|
9438
|
+
options: {
|
|
9439
|
+
...this.config,
|
|
9440
|
+
autoDecrypt: false,
|
|
9441
|
+
// Disable decryption
|
|
9442
|
+
autoEncrypt: false
|
|
9443
|
+
// Disable encryption
|
|
9444
|
+
}
|
|
9445
|
+
});
|
|
9446
|
+
let metadata = await tempSchema.unmapper(request.Metadata);
|
|
9447
|
+
const behaviorImpl = getBehavior(this.behavior);
|
|
9448
|
+
let body = "";
|
|
9449
|
+
if (request.ContentLength > 0) {
|
|
9450
|
+
try {
|
|
9451
|
+
const fullObject = await this.client.getObject(key);
|
|
9452
|
+
body = await streamToString(fullObject.Body);
|
|
9453
|
+
} catch (bodyError) {
|
|
9454
|
+
console.warn(`Failed to read body for resource ${id}:`, bodyError.message);
|
|
9455
|
+
body = "";
|
|
9456
|
+
}
|
|
9457
|
+
}
|
|
9458
|
+
const { metadata: processedMetadata } = await behaviorImpl.handleGet({
|
|
9459
|
+
resource: this,
|
|
9460
|
+
metadata,
|
|
9461
|
+
body
|
|
9462
|
+
});
|
|
9463
|
+
let data = processedMetadata;
|
|
9464
|
+
data.id = id;
|
|
9465
|
+
data._contentLength = request.ContentLength;
|
|
9466
|
+
data._lastModified = request.LastModified;
|
|
9467
|
+
data._hasContent = request.ContentLength > 0;
|
|
9468
|
+
data._mimeType = request.ContentType || null;
|
|
9469
|
+
data._version = objectVersion;
|
|
9470
|
+
data._decryptionFailed = true;
|
|
9471
|
+
if (request.VersionId) data._versionId = request.VersionId;
|
|
9472
|
+
if (request.Expiration) data._expiresAt = request.Expiration;
|
|
9473
|
+
data._definitionHash = this.getDefinitionHash();
|
|
9474
|
+
this.emit("get", data);
|
|
9475
|
+
return data;
|
|
9476
|
+
} catch (fallbackError) {
|
|
9477
|
+
console.error(`Fallback attempt also failed for resource ${id}:`, fallbackError.message);
|
|
9478
|
+
}
|
|
9479
|
+
}
|
|
9270
9480
|
const enhancedError = new Error(`Failed to get resource with id '${id}': ${error.message}`);
|
|
9271
9481
|
enhancedError.originalError = error;
|
|
9272
9482
|
enhancedError.resourceId = id;
|
|
@@ -9313,7 +9523,7 @@ class Resource extends EventEmitter {
|
|
|
9313
9523
|
*/
|
|
9314
9524
|
async update(id, attributes) {
|
|
9315
9525
|
const live = await this.get(id);
|
|
9316
|
-
if (this.
|
|
9526
|
+
if (this.config.timestamps) {
|
|
9317
9527
|
attributes.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9318
9528
|
}
|
|
9319
9529
|
const preProcessedData = await this.executeHooks("preUpdate", attributes);
|
|
@@ -9435,7 +9645,7 @@ class Resource extends EventEmitter {
|
|
|
9435
9645
|
async count({ partition = null, partitionValues = {} } = {}) {
|
|
9436
9646
|
let prefix;
|
|
9437
9647
|
if (partition && Object.keys(partitionValues).length > 0) {
|
|
9438
|
-
const partitionDef = this.
|
|
9648
|
+
const partitionDef = this.config.partitions[partition];
|
|
9439
9649
|
if (!partitionDef) {
|
|
9440
9650
|
throw new Error(`Partition '${partition}' not found`);
|
|
9441
9651
|
}
|
|
@@ -9520,9 +9730,9 @@ class Resource extends EventEmitter {
|
|
|
9520
9730
|
return results;
|
|
9521
9731
|
}
|
|
9522
9732
|
async deleteAll() {
|
|
9523
|
-
if (this.
|
|
9733
|
+
if (this.config.paranoid !== false) {
|
|
9524
9734
|
throw new Error(
|
|
9525
|
-
`deleteAll() is a dangerous operation and requires paranoid: false option. Current paranoid setting: ${this.
|
|
9735
|
+
`deleteAll() is a dangerous operation and requires paranoid: false option. Current paranoid setting: ${this.config.paranoid}`
|
|
9526
9736
|
);
|
|
9527
9737
|
}
|
|
9528
9738
|
const prefix = `resource=${this.name}/v=${this.version}`;
|
|
@@ -9539,9 +9749,9 @@ class Resource extends EventEmitter {
|
|
|
9539
9749
|
* @returns {Promise<Object>} Deletion report
|
|
9540
9750
|
*/
|
|
9541
9751
|
async deleteAllData() {
|
|
9542
|
-
if (this.
|
|
9752
|
+
if (this.config.paranoid !== false) {
|
|
9543
9753
|
throw new Error(
|
|
9544
|
-
`deleteAllData() is a dangerous operation and requires paranoid: false option. Current paranoid setting: ${this.
|
|
9754
|
+
`deleteAllData() is a dangerous operation and requires paranoid: false option. Current paranoid setting: ${this.config.paranoid}`
|
|
9545
9755
|
);
|
|
9546
9756
|
}
|
|
9547
9757
|
const prefix = `resource=${this.name}`;
|
|
@@ -9584,10 +9794,10 @@ class Resource extends EventEmitter {
|
|
|
9584
9794
|
async listIds({ partition = null, partitionValues = {}, limit, offset = 0 } = {}) {
|
|
9585
9795
|
let prefix;
|
|
9586
9796
|
if (partition && Object.keys(partitionValues).length > 0) {
|
|
9587
|
-
|
|
9588
|
-
if (!partitionDef) {
|
|
9797
|
+
if (!this.config.partitions || !this.config.partitions[partition]) {
|
|
9589
9798
|
throw new Error(`Partition '${partition}' not found`);
|
|
9590
9799
|
}
|
|
9800
|
+
const partitionDef = this.config.partitions[partition];
|
|
9591
9801
|
const partitionSegments = [];
|
|
9592
9802
|
const sortedFields = Object.entries(partitionDef.fields).sort(([a], [b]) => a.localeCompare(b));
|
|
9593
9803
|
for (const [fieldName, rule] of sortedFields) {
|
|
@@ -9656,16 +9866,32 @@ class Resource extends EventEmitter {
|
|
|
9656
9866
|
if (limit) {
|
|
9657
9867
|
filteredIds2 = filteredIds2.slice(0, limit);
|
|
9658
9868
|
}
|
|
9659
|
-
const { results: results2 } = await PromisePool.for(filteredIds2).withConcurrency(this.parallelism).
|
|
9660
|
-
|
|
9869
|
+
const { results: results2, errors: errors2 } = await PromisePool.for(filteredIds2).withConcurrency(this.parallelism).handleError(async (error, id) => {
|
|
9870
|
+
console.warn(`Failed to get resource ${id}:`, error.message);
|
|
9871
|
+
return null;
|
|
9872
|
+
}).process(async (id) => {
|
|
9873
|
+
try {
|
|
9874
|
+
return await this.get(id);
|
|
9875
|
+
} catch (error) {
|
|
9876
|
+
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9877
|
+
console.warn(`Decryption failed for ${id}, returning basic info`);
|
|
9878
|
+
return {
|
|
9879
|
+
id,
|
|
9880
|
+
_decryptionFailed: true,
|
|
9881
|
+
_error: error.message
|
|
9882
|
+
};
|
|
9883
|
+
}
|
|
9884
|
+
throw error;
|
|
9885
|
+
}
|
|
9661
9886
|
});
|
|
9662
|
-
|
|
9663
|
-
|
|
9887
|
+
const validResults2 = results2.filter((item) => item !== null);
|
|
9888
|
+
this.emit("list", { partition, partitionValues, count: validResults2.length, errors: errors2.length });
|
|
9889
|
+
return validResults2;
|
|
9664
9890
|
}
|
|
9665
|
-
|
|
9666
|
-
if (!partitionDef) {
|
|
9891
|
+
if (!this.config.partitions || !this.config.partitions[partition]) {
|
|
9667
9892
|
throw new Error(`Partition '${partition}' not found`);
|
|
9668
9893
|
}
|
|
9894
|
+
const partitionDef = this.config.partitions[partition];
|
|
9669
9895
|
const partitionSegments = [];
|
|
9670
9896
|
const sortedFields = Object.entries(partitionDef.fields).sort(([a], [b]) => a.localeCompare(b));
|
|
9671
9897
|
for (const [fieldName, rule] of sortedFields) {
|
|
@@ -9691,11 +9917,29 @@ class Resource extends EventEmitter {
|
|
|
9691
9917
|
if (limit) {
|
|
9692
9918
|
filteredIds = filteredIds.slice(0, limit);
|
|
9693
9919
|
}
|
|
9694
|
-
const { results } = await PromisePool.for(filteredIds).withConcurrency(this.parallelism).
|
|
9695
|
-
|
|
9920
|
+
const { results, errors } = await PromisePool.for(filteredIds).withConcurrency(this.parallelism).handleError(async (error, id) => {
|
|
9921
|
+
console.warn(`Failed to get partition resource ${id}:`, error.message);
|
|
9922
|
+
return null;
|
|
9923
|
+
}).process(async (id) => {
|
|
9924
|
+
try {
|
|
9925
|
+
return await this.getFromPartition({ id, partitionName: partition, partitionValues });
|
|
9926
|
+
} catch (error) {
|
|
9927
|
+
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9928
|
+
console.warn(`Decryption failed for partition resource ${id}, returning basic info`);
|
|
9929
|
+
return {
|
|
9930
|
+
id,
|
|
9931
|
+
_partition: partition,
|
|
9932
|
+
_partitionValues: partitionValues,
|
|
9933
|
+
_decryptionFailed: true,
|
|
9934
|
+
_error: error.message
|
|
9935
|
+
};
|
|
9936
|
+
}
|
|
9937
|
+
throw error;
|
|
9938
|
+
}
|
|
9696
9939
|
});
|
|
9697
|
-
|
|
9698
|
-
|
|
9940
|
+
const validResults = results.filter((item) => item !== null);
|
|
9941
|
+
this.emit("list", { partition, partitionValues, count: validResults.length, errors: errors.length });
|
|
9942
|
+
return validResults;
|
|
9699
9943
|
}
|
|
9700
9944
|
/**
|
|
9701
9945
|
* Get multiple resources by their IDs
|
|
@@ -9706,11 +9950,30 @@ class Resource extends EventEmitter {
|
|
|
9706
9950
|
* users.forEach(user => console.log(user.name));
|
|
9707
9951
|
*/
|
|
9708
9952
|
async getMany(ids) {
|
|
9709
|
-
const { results } = await PromisePool.for(ids).withConcurrency(this.client.parallelism).
|
|
9953
|
+
const { results, errors } = await PromisePool.for(ids).withConcurrency(this.client.parallelism).handleError(async (error, id) => {
|
|
9954
|
+
console.warn(`Failed to get resource ${id}:`, error.message);
|
|
9955
|
+
return {
|
|
9956
|
+
id,
|
|
9957
|
+
_error: error.message,
|
|
9958
|
+
_decryptionFailed: error.message.includes("Cipher job failed") || error.message.includes("OperationError")
|
|
9959
|
+
};
|
|
9960
|
+
}).process(async (id) => {
|
|
9710
9961
|
this.emit("id", id);
|
|
9711
|
-
|
|
9712
|
-
|
|
9713
|
-
|
|
9962
|
+
try {
|
|
9963
|
+
const data = await this.get(id);
|
|
9964
|
+
this.emit("data", data);
|
|
9965
|
+
return data;
|
|
9966
|
+
} catch (error) {
|
|
9967
|
+
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9968
|
+
console.warn(`Decryption failed for ${id}, returning basic info`);
|
|
9969
|
+
return {
|
|
9970
|
+
id,
|
|
9971
|
+
_decryptionFailed: true,
|
|
9972
|
+
_error: error.message
|
|
9973
|
+
};
|
|
9974
|
+
}
|
|
9975
|
+
throw error;
|
|
9976
|
+
}
|
|
9714
9977
|
});
|
|
9715
9978
|
this.emit("getMany", ids.length);
|
|
9716
9979
|
return results;
|
|
@@ -9725,9 +9988,28 @@ class Resource extends EventEmitter {
|
|
|
9725
9988
|
async getAll() {
|
|
9726
9989
|
let ids = await this.listIds();
|
|
9727
9990
|
if (ids.length === 0) return [];
|
|
9728
|
-
const { results } = await PromisePool.for(ids).withConcurrency(this.client.parallelism).
|
|
9729
|
-
|
|
9730
|
-
return
|
|
9991
|
+
const { results, errors } = await PromisePool.for(ids).withConcurrency(this.client.parallelism).handleError(async (error, id) => {
|
|
9992
|
+
console.warn(`Failed to get resource ${id}:`, error.message);
|
|
9993
|
+
return {
|
|
9994
|
+
id,
|
|
9995
|
+
_error: error.message,
|
|
9996
|
+
_decryptionFailed: error.message.includes("Cipher job failed") || error.message.includes("OperationError")
|
|
9997
|
+
};
|
|
9998
|
+
}).process(async (id) => {
|
|
9999
|
+
try {
|
|
10000
|
+
const data = await this.get(id);
|
|
10001
|
+
return data;
|
|
10002
|
+
} catch (error) {
|
|
10003
|
+
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
10004
|
+
console.warn(`Decryption failed for ${id}, returning basic info`);
|
|
10005
|
+
return {
|
|
10006
|
+
id,
|
|
10007
|
+
_decryptionFailed: true,
|
|
10008
|
+
_error: error.message
|
|
10009
|
+
};
|
|
10010
|
+
}
|
|
10011
|
+
throw error;
|
|
10012
|
+
}
|
|
9731
10013
|
});
|
|
9732
10014
|
this.emit("getAll", results.length);
|
|
9733
10015
|
return results;
|
|
@@ -9914,16 +10196,14 @@ class Resource extends EventEmitter {
|
|
|
9914
10196
|
}
|
|
9915
10197
|
/**
|
|
9916
10198
|
* Generate definition hash for this resource
|
|
9917
|
-
* @returns {string} SHA256 hash of the
|
|
10199
|
+
* @returns {string} SHA256 hash of the resource definition (name + attributes)
|
|
9918
10200
|
*/
|
|
9919
10201
|
getDefinitionHash() {
|
|
9920
|
-
const
|
|
9921
|
-
|
|
9922
|
-
|
|
9923
|
-
|
|
9924
|
-
|
|
9925
|
-
}
|
|
9926
|
-
const stableString = jsonStableStringify(stableAttributes);
|
|
10202
|
+
const definition = {
|
|
10203
|
+
attributes: this.attributes,
|
|
10204
|
+
behavior: this.behavior
|
|
10205
|
+
};
|
|
10206
|
+
const stableString = jsonStableStringify(definition);
|
|
9927
10207
|
return `sha256:${createHash("sha256").update(stableString).digest("hex")}`;
|
|
9928
10208
|
}
|
|
9929
10209
|
/**
|
|
@@ -9942,14 +10222,34 @@ class Resource extends EventEmitter {
|
|
|
9942
10222
|
* @returns {Object} Schema object for the version
|
|
9943
10223
|
*/
|
|
9944
10224
|
async getSchemaForVersion(version) {
|
|
9945
|
-
|
|
10225
|
+
if (version === this.version) {
|
|
10226
|
+
return this.schema;
|
|
10227
|
+
}
|
|
10228
|
+
try {
|
|
10229
|
+
const compatibleSchema = new Schema({
|
|
10230
|
+
name: this.name,
|
|
10231
|
+
attributes: this.attributes,
|
|
10232
|
+
passphrase: this.passphrase,
|
|
10233
|
+
version,
|
|
10234
|
+
options: {
|
|
10235
|
+
...this.config,
|
|
10236
|
+
// For older versions, be more lenient with decryption
|
|
10237
|
+
autoDecrypt: true,
|
|
10238
|
+
autoEncrypt: true
|
|
10239
|
+
}
|
|
10240
|
+
});
|
|
10241
|
+
return compatibleSchema;
|
|
10242
|
+
} catch (error) {
|
|
10243
|
+
console.warn(`Failed to create compatible schema for version ${version}, using current schema:`, error.message);
|
|
10244
|
+
return this.schema;
|
|
10245
|
+
}
|
|
9946
10246
|
}
|
|
9947
10247
|
/**
|
|
9948
10248
|
* Create partition references after insert
|
|
9949
10249
|
* @param {Object} data - Inserted object data
|
|
9950
10250
|
*/
|
|
9951
10251
|
async createPartitionReferences(data) {
|
|
9952
|
-
const partitions = this.
|
|
10252
|
+
const partitions = this.config.partitions;
|
|
9953
10253
|
if (!partitions || Object.keys(partitions).length === 0) {
|
|
9954
10254
|
return;
|
|
9955
10255
|
}
|
|
@@ -9980,7 +10280,7 @@ class Resource extends EventEmitter {
|
|
|
9980
10280
|
* @param {Object} data - Deleted object data
|
|
9981
10281
|
*/
|
|
9982
10282
|
async deletePartitionReferences(data) {
|
|
9983
|
-
const partitions = this.
|
|
10283
|
+
const partitions = this.config.partitions;
|
|
9984
10284
|
if (!partitions || Object.keys(partitions).length === 0) {
|
|
9985
10285
|
return;
|
|
9986
10286
|
}
|
|
@@ -10072,7 +10372,7 @@ class Resource extends EventEmitter {
|
|
|
10072
10372
|
* @param {Object} data - Updated object data
|
|
10073
10373
|
*/
|
|
10074
10374
|
async updatePartitionReferences(data) {
|
|
10075
|
-
const partitions = this.
|
|
10375
|
+
const partitions = this.config.partitions;
|
|
10076
10376
|
if (!partitions || Object.keys(partitions).length === 0) {
|
|
10077
10377
|
return;
|
|
10078
10378
|
}
|
|
@@ -10128,10 +10428,10 @@ class Resource extends EventEmitter {
|
|
|
10128
10428
|
* });
|
|
10129
10429
|
*/
|
|
10130
10430
|
async getFromPartition({ id, partitionName, partitionValues = {} }) {
|
|
10131
|
-
|
|
10132
|
-
if (!partition) {
|
|
10431
|
+
if (!this.config.partitions || !this.config.partitions[partitionName]) {
|
|
10133
10432
|
throw new Error(`Partition '${partitionName}' not found`);
|
|
10134
10433
|
}
|
|
10434
|
+
const partition = this.config.partitions[partitionName];
|
|
10135
10435
|
const partitionSegments = [];
|
|
10136
10436
|
const sortedFields = Object.entries(partition.fields).sort(([a], [b]) => a.localeCompare(b));
|
|
10137
10437
|
for (const [fieldName, rule] of sortedFields) {
|
|
@@ -10179,6 +10479,98 @@ class Resource extends EventEmitter {
|
|
|
10179
10479
|
return data;
|
|
10180
10480
|
}
|
|
10181
10481
|
}
|
|
10482
|
+
function validateResourceConfig(config) {
|
|
10483
|
+
const errors = [];
|
|
10484
|
+
if (!config.name) {
|
|
10485
|
+
errors.push("Resource 'name' is required");
|
|
10486
|
+
} else if (typeof config.name !== "string") {
|
|
10487
|
+
errors.push("Resource 'name' must be a string");
|
|
10488
|
+
} else if (config.name.trim() === "") {
|
|
10489
|
+
errors.push("Resource 'name' cannot be empty");
|
|
10490
|
+
}
|
|
10491
|
+
if (!config.client) {
|
|
10492
|
+
errors.push("S3 'client' is required");
|
|
10493
|
+
}
|
|
10494
|
+
if (!config.attributes) {
|
|
10495
|
+
errors.push("Resource 'attributes' are required");
|
|
10496
|
+
} else if (typeof config.attributes !== "object" || Array.isArray(config.attributes)) {
|
|
10497
|
+
errors.push("Resource 'attributes' must be an object");
|
|
10498
|
+
} else if (Object.keys(config.attributes).length === 0) {
|
|
10499
|
+
errors.push("Resource 'attributes' cannot be empty");
|
|
10500
|
+
}
|
|
10501
|
+
if (config.version !== void 0 && typeof config.version !== "string") {
|
|
10502
|
+
errors.push("Resource 'version' must be a string");
|
|
10503
|
+
}
|
|
10504
|
+
if (config.behavior !== void 0 && typeof config.behavior !== "string") {
|
|
10505
|
+
errors.push("Resource 'behavior' must be a string");
|
|
10506
|
+
}
|
|
10507
|
+
if (config.passphrase !== void 0 && typeof config.passphrase !== "string") {
|
|
10508
|
+
errors.push("Resource 'passphrase' must be a string");
|
|
10509
|
+
}
|
|
10510
|
+
if (config.parallelism !== void 0) {
|
|
10511
|
+
if (typeof config.parallelism !== "number" || !Number.isInteger(config.parallelism)) {
|
|
10512
|
+
errors.push("Resource 'parallelism' must be an integer");
|
|
10513
|
+
} else if (config.parallelism < 1) {
|
|
10514
|
+
errors.push("Resource 'parallelism' must be greater than 0");
|
|
10515
|
+
}
|
|
10516
|
+
}
|
|
10517
|
+
if (config.observers !== void 0 && !Array.isArray(config.observers)) {
|
|
10518
|
+
errors.push("Resource 'observers' must be an array");
|
|
10519
|
+
}
|
|
10520
|
+
const booleanFields = ["cache", "autoDecrypt", "timestamps", "paranoid", "allNestedObjectsOptional"];
|
|
10521
|
+
for (const field of booleanFields) {
|
|
10522
|
+
if (config[field] !== void 0 && typeof config[field] !== "boolean") {
|
|
10523
|
+
errors.push(`Resource '${field}' must be a boolean`);
|
|
10524
|
+
}
|
|
10525
|
+
}
|
|
10526
|
+
if (config.partitions !== void 0) {
|
|
10527
|
+
if (typeof config.partitions !== "object" || Array.isArray(config.partitions)) {
|
|
10528
|
+
errors.push("Resource 'partitions' must be an object");
|
|
10529
|
+
} else {
|
|
10530
|
+
for (const [partitionName, partitionDef] of Object.entries(config.partitions)) {
|
|
10531
|
+
if (typeof partitionDef !== "object" || Array.isArray(partitionDef)) {
|
|
10532
|
+
errors.push(`Partition '${partitionName}' must be an object`);
|
|
10533
|
+
} else if (!partitionDef.fields) {
|
|
10534
|
+
errors.push(`Partition '${partitionName}' must have a 'fields' property`);
|
|
10535
|
+
} else if (typeof partitionDef.fields !== "object" || Array.isArray(partitionDef.fields)) {
|
|
10536
|
+
errors.push(`Partition '${partitionName}.fields' must be an object`);
|
|
10537
|
+
} else {
|
|
10538
|
+
for (const [fieldName, fieldType] of Object.entries(partitionDef.fields)) {
|
|
10539
|
+
if (typeof fieldType !== "string") {
|
|
10540
|
+
errors.push(`Partition '${partitionName}.fields.${fieldName}' must be a string`);
|
|
10541
|
+
}
|
|
10542
|
+
}
|
|
10543
|
+
}
|
|
10544
|
+
}
|
|
10545
|
+
}
|
|
10546
|
+
}
|
|
10547
|
+
if (config.hooks !== void 0) {
|
|
10548
|
+
if (typeof config.hooks !== "object" || Array.isArray(config.hooks)) {
|
|
10549
|
+
errors.push("Resource 'hooks' must be an object");
|
|
10550
|
+
} else {
|
|
10551
|
+
const validHookEvents = ["preInsert", "afterInsert", "preUpdate", "afterUpdate", "preDelete", "afterDelete"];
|
|
10552
|
+
for (const [event, hooksArr] of Object.entries(config.hooks)) {
|
|
10553
|
+
if (!validHookEvents.includes(event)) {
|
|
10554
|
+
errors.push(`Invalid hook event '${event}'. Valid events: ${validHookEvents.join(", ")}`);
|
|
10555
|
+
} else if (!Array.isArray(hooksArr)) {
|
|
10556
|
+
errors.push(`Resource 'hooks.${event}' must be an array`);
|
|
10557
|
+
} else {
|
|
10558
|
+
for (let i = 0; i < hooksArr.length; i++) {
|
|
10559
|
+
const hook = hooksArr[i];
|
|
10560
|
+
if (typeof hook !== "function") {
|
|
10561
|
+
if (typeof hook === "string") continue;
|
|
10562
|
+
continue;
|
|
10563
|
+
}
|
|
10564
|
+
}
|
|
10565
|
+
}
|
|
10566
|
+
}
|
|
10567
|
+
}
|
|
10568
|
+
}
|
|
10569
|
+
return {
|
|
10570
|
+
isValid: errors.length === 0,
|
|
10571
|
+
errors
|
|
10572
|
+
};
|
|
10573
|
+
}
|
|
10182
10574
|
|
|
10183
10575
|
class Database extends EventEmitter {
|
|
10184
10576
|
constructor(options) {
|
|
@@ -10186,7 +10578,7 @@ class Database extends EventEmitter {
|
|
|
10186
10578
|
this.version = "1";
|
|
10187
10579
|
this.s3dbVersion = (() => {
|
|
10188
10580
|
try {
|
|
10189
|
-
return true ? "4.1.
|
|
10581
|
+
return true ? "4.1.10" : "latest";
|
|
10190
10582
|
} catch (e) {
|
|
10191
10583
|
return "latest";
|
|
10192
10584
|
}
|
|
@@ -10199,10 +10591,21 @@ class Database extends EventEmitter {
|
|
|
10199
10591
|
this.plugins = options.plugins || [];
|
|
10200
10592
|
this.cache = options.cache;
|
|
10201
10593
|
this.passphrase = options.passphrase || "secret";
|
|
10594
|
+
let connectionString = options.connectionString;
|
|
10595
|
+
if (!connectionString && (options.bucket || options.accessKeyId || options.secretAccessKey)) {
|
|
10596
|
+
connectionString = ConnectionString.buildFromParams({
|
|
10597
|
+
bucket: options.bucket,
|
|
10598
|
+
region: options.region,
|
|
10599
|
+
accessKeyId: options.accessKeyId,
|
|
10600
|
+
secretAccessKey: options.secretAccessKey,
|
|
10601
|
+
endpoint: options.endpoint,
|
|
10602
|
+
forcePathStyle: options.forcePathStyle
|
|
10603
|
+
});
|
|
10604
|
+
}
|
|
10202
10605
|
this.client = options.client || new Client({
|
|
10203
10606
|
verbose: this.verbose,
|
|
10204
10607
|
parallelism: this.parallelism,
|
|
10205
|
-
connectionString
|
|
10608
|
+
connectionString
|
|
10206
10609
|
});
|
|
10207
10610
|
this.bucket = this.client.bucket;
|
|
10208
10611
|
this.keyPrefix = this.client.keyPrefix;
|
|
@@ -10227,15 +10630,18 @@ class Database extends EventEmitter {
|
|
|
10227
10630
|
name,
|
|
10228
10631
|
client: this.client,
|
|
10229
10632
|
version: currentVersion,
|
|
10230
|
-
options: {
|
|
10231
|
-
...versionData.options,
|
|
10232
|
-
partitions: resourceMetadata.partitions || versionData.options?.partitions || {}
|
|
10233
|
-
},
|
|
10234
10633
|
attributes: versionData.attributes,
|
|
10235
10634
|
behavior: versionData.behavior || "user-management",
|
|
10236
10635
|
parallelism: this.parallelism,
|
|
10237
10636
|
passphrase: this.passphrase,
|
|
10238
|
-
observers: [this]
|
|
10637
|
+
observers: [this],
|
|
10638
|
+
cache: this.cache,
|
|
10639
|
+
timestamps: versionData.options?.timestamps || false,
|
|
10640
|
+
partitions: resourceMetadata.partitions || versionData.options?.partitions || {},
|
|
10641
|
+
paranoid: versionData.options?.paranoid !== false,
|
|
10642
|
+
allNestedObjectsOptional: versionData.options?.allNestedObjectsOptional || false,
|
|
10643
|
+
autoDecrypt: versionData.options?.autoDecrypt !== false,
|
|
10644
|
+
hooks: {}
|
|
10239
10645
|
});
|
|
10240
10646
|
}
|
|
10241
10647
|
}
|
|
@@ -10304,7 +10710,7 @@ class Database extends EventEmitter {
|
|
|
10304
10710
|
generateDefinitionHash(definition, behavior = void 0) {
|
|
10305
10711
|
const attributes = definition.attributes;
|
|
10306
10712
|
const stableAttributes = { ...attributes };
|
|
10307
|
-
if (definition.
|
|
10713
|
+
if (definition.timestamps) {
|
|
10308
10714
|
delete stableAttributes.createdAt;
|
|
10309
10715
|
delete stableAttributes.updatedAt;
|
|
10310
10716
|
}
|
|
@@ -10366,14 +10772,21 @@ class Database extends EventEmitter {
|
|
|
10366
10772
|
}
|
|
10367
10773
|
metadata.resources[name] = {
|
|
10368
10774
|
currentVersion: version,
|
|
10369
|
-
partitions:
|
|
10775
|
+
partitions: resource.config.partitions || {},
|
|
10370
10776
|
versions: {
|
|
10371
10777
|
...existingResource?.versions,
|
|
10372
10778
|
// Preserve previous versions
|
|
10373
10779
|
[version]: {
|
|
10374
10780
|
hash: definitionHash,
|
|
10375
10781
|
attributes: resourceDef.attributes,
|
|
10376
|
-
options:
|
|
10782
|
+
options: {
|
|
10783
|
+
timestamps: resource.config.timestamps,
|
|
10784
|
+
partitions: resource.config.partitions,
|
|
10785
|
+
paranoid: resource.config.paranoid,
|
|
10786
|
+
allNestedObjectsOptional: resource.config.allNestedObjectsOptional,
|
|
10787
|
+
autoDecrypt: resource.config.autoDecrypt,
|
|
10788
|
+
cache: resource.config.cache
|
|
10789
|
+
},
|
|
10377
10790
|
behavior: resourceDef.behavior || "user-management",
|
|
10378
10791
|
createdAt: isNewVersion ? (/* @__PURE__ */ new Date()).toISOString() : existingVersionData?.createdAt
|
|
10379
10792
|
}
|
|
@@ -10426,10 +10839,9 @@ class Database extends EventEmitter {
|
|
|
10426
10839
|
observers: [],
|
|
10427
10840
|
client: this.client,
|
|
10428
10841
|
version: "temp",
|
|
10429
|
-
|
|
10430
|
-
|
|
10431
|
-
|
|
10432
|
-
}
|
|
10842
|
+
passphrase: this.passphrase,
|
|
10843
|
+
cache: this.cache,
|
|
10844
|
+
...options
|
|
10433
10845
|
});
|
|
10434
10846
|
const newHash = this.generateDefinitionHash(tempResource.export(), behavior);
|
|
10435
10847
|
const existingHash = this.generateDefinitionHash(this.resources[name].export(), this.resources[name].behavior);
|
|
@@ -10469,7 +10881,7 @@ class Database extends EventEmitter {
|
|
|
10469
10881
|
async createResource({ name, attributes, options = {}, behavior = "user-management" }) {
|
|
10470
10882
|
if (this.resources[name]) {
|
|
10471
10883
|
const existingResource = this.resources[name];
|
|
10472
|
-
Object.assign(existingResource.
|
|
10884
|
+
Object.assign(existingResource.config, {
|
|
10473
10885
|
cache: this.cache,
|
|
10474
10886
|
...options
|
|
10475
10887
|
});
|
|
@@ -10496,10 +10908,9 @@ class Database extends EventEmitter {
|
|
|
10496
10908
|
observers: [this],
|
|
10497
10909
|
client: this.client,
|
|
10498
10910
|
version,
|
|
10499
|
-
|
|
10500
|
-
|
|
10501
|
-
|
|
10502
|
-
}
|
|
10911
|
+
passphrase: this.passphrase,
|
|
10912
|
+
cache: this.cache,
|
|
10913
|
+
...options
|
|
10503
10914
|
});
|
|
10504
10915
|
this.resources[name] = resource;
|
|
10505
10916
|
await this.uploadMetadataFile();
|
|
@@ -17201,4 +17612,4 @@ class CachePlugin extends Plugin {
|
|
|
17201
17612
|
}
|
|
17202
17613
|
}
|
|
17203
17614
|
|
|
17204
|
-
export { BaseError, Cache, CachePlugin, Client, ConnectionString, CostsPlugin, Database, ErrorMap, InvalidResourceItem, MemoryCache, MissingMetadata, NoSuchBucket, NoSuchKey, NotFound, Plugin, PluginObject, ResourceIdsPageReader, ResourceIdsReader, ResourceReader, ResourceWriter, S3Cache, S3_DEFAULT_ENDPOINT, S3_DEFAULT_REGION, S3db, UnknownError, Validator, ValidatorManager, decrypt, encrypt, sha256, streamToString };
|
|
17615
|
+
export { AuthenticationError, BaseError, Cache, CachePlugin, Client, ConnectionString, CostsPlugin, Database, DatabaseError, EncryptionError, ErrorMap, InvalidResourceItem, MemoryCache, MissingMetadata, NoSuchBucket, NoSuchKey, NotFound, PermissionError, Plugin, PluginObject, ResourceIdsPageReader, ResourceIdsReader, ResourceNotFound, ResourceNotFound as ResourceNotFoundError, ResourceReader, ResourceWriter, S3Cache, S3db as S3DB, S3DBError, S3_DEFAULT_ENDPOINT, S3_DEFAULT_REGION, S3db, S3DBError as S3dbError, UnknownError, ValidationError, Validator, ValidatorManager, decrypt, S3db as default, encrypt, sha256, streamToString };
|