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.iife.js
CHANGED
|
@@ -236,6 +236,8 @@ var S3DB = (function (exports, nanoid, lodashEs, promisePool, clientS3, crypto,
|
|
|
236
236
|
;
|
|
237
237
|
|
|
238
238
|
const idGenerator = nanoid.customAlphabet(nanoid.urlAlphabet, 22);
|
|
239
|
+
const passwordAlphabet = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789";
|
|
240
|
+
nanoid.customAlphabet(passwordAlphabet, 12);
|
|
239
241
|
|
|
240
242
|
var domain;
|
|
241
243
|
|
|
@@ -736,42 +738,85 @@ ${JSON.stringify(rest, null, 2)}`;
|
|
|
736
738
|
return `${this.name} | ${this.message}`;
|
|
737
739
|
}
|
|
738
740
|
}
|
|
739
|
-
class
|
|
741
|
+
class S3DBError extends BaseError {
|
|
742
|
+
constructor(message, details = {}) {
|
|
743
|
+
super({ message, ...details });
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
class DatabaseError extends S3DBError {
|
|
747
|
+
constructor(message, details = {}) {
|
|
748
|
+
super(message, details);
|
|
749
|
+
Object.assign(this, details);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
class ValidationError extends S3DBError {
|
|
753
|
+
constructor(message, details = {}) {
|
|
754
|
+
super(message, details);
|
|
755
|
+
Object.assign(this, details);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
class AuthenticationError extends S3DBError {
|
|
759
|
+
constructor(message, details = {}) {
|
|
760
|
+
super(message, details);
|
|
761
|
+
Object.assign(this, details);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
class PermissionError extends S3DBError {
|
|
765
|
+
constructor(message, details = {}) {
|
|
766
|
+
super(message, details);
|
|
767
|
+
Object.assign(this, details);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
class EncryptionError extends S3DBError {
|
|
771
|
+
constructor(message, details = {}) {
|
|
772
|
+
super(message, details);
|
|
773
|
+
Object.assign(this, details);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
class ResourceNotFound extends S3DBError {
|
|
777
|
+
constructor({ bucket, resourceName, id, ...rest }) {
|
|
778
|
+
super(`Resource not found: ${resourceName}/${id} [bucket:${bucket}]`, {
|
|
779
|
+
bucket,
|
|
780
|
+
resourceName,
|
|
781
|
+
id,
|
|
782
|
+
...rest
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
class NoSuchBucket extends S3DBError {
|
|
740
787
|
constructor({ bucket, ...rest }) {
|
|
741
|
-
super(
|
|
788
|
+
super(`Bucket does not exists [bucket:${bucket}]`, { bucket, ...rest });
|
|
742
789
|
}
|
|
743
790
|
}
|
|
744
|
-
class NoSuchKey extends
|
|
791
|
+
class NoSuchKey extends S3DBError {
|
|
745
792
|
constructor({ bucket, key, ...rest }) {
|
|
746
|
-
super(
|
|
747
|
-
this.key = key;
|
|
793
|
+
super(`Key [${key}] does not exists [bucket:${bucket}/${key}]`, { bucket, key, ...rest });
|
|
748
794
|
}
|
|
749
795
|
}
|
|
750
796
|
class NotFound extends NoSuchKey {
|
|
751
797
|
}
|
|
752
|
-
class MissingMetadata extends
|
|
798
|
+
class MissingMetadata extends S3DBError {
|
|
753
799
|
constructor({ bucket, ...rest }) {
|
|
754
|
-
super(
|
|
800
|
+
super(`Missing metadata for bucket [bucket:${bucket}]`, { bucket, ...rest });
|
|
755
801
|
}
|
|
756
802
|
}
|
|
757
|
-
class InvalidResourceItem extends
|
|
803
|
+
class InvalidResourceItem extends S3DBError {
|
|
758
804
|
constructor({
|
|
759
805
|
bucket,
|
|
760
806
|
resourceName,
|
|
761
807
|
attributes,
|
|
762
808
|
validation
|
|
763
809
|
}) {
|
|
764
|
-
super({
|
|
810
|
+
super(`This item is not valid. Resource=${resourceName} [bucket:${bucket}].
|
|
811
|
+
${JSON.stringify(validation, null, 2)}`, {
|
|
765
812
|
bucket,
|
|
766
|
-
|
|
767
|
-
|
|
813
|
+
resourceName,
|
|
814
|
+
attributes,
|
|
815
|
+
validation
|
|
768
816
|
});
|
|
769
|
-
this.resourceName = resourceName;
|
|
770
|
-
this.attributes = attributes;
|
|
771
|
-
this.validation = validation;
|
|
772
817
|
}
|
|
773
818
|
}
|
|
774
|
-
class UnknownError extends
|
|
819
|
+
class UnknownError extends S3DBError {
|
|
775
820
|
}
|
|
776
821
|
const ErrorMap = {
|
|
777
822
|
"NotFound": NotFound,
|
|
@@ -800,9 +845,9 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
800
845
|
}
|
|
801
846
|
}
|
|
802
847
|
defineS3(uri) {
|
|
803
|
-
this.bucket = uri.hostname;
|
|
804
|
-
this.accessKeyId = uri.username;
|
|
805
|
-
this.secretAccessKey = uri.password;
|
|
848
|
+
this.bucket = decodeURIComponent(uri.hostname);
|
|
849
|
+
this.accessKeyId = decodeURIComponent(uri.username);
|
|
850
|
+
this.secretAccessKey = decodeURIComponent(uri.password);
|
|
806
851
|
this.endpoint = S3_DEFAULT_ENDPOINT;
|
|
807
852
|
if (["/", "", null].includes(uri.pathname)) {
|
|
808
853
|
this.keyPrefix = "";
|
|
@@ -814,14 +859,14 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
814
859
|
defineMinio(uri) {
|
|
815
860
|
this.forcePathStyle = true;
|
|
816
861
|
this.endpoint = uri.origin;
|
|
817
|
-
this.accessKeyId = uri.username;
|
|
818
|
-
this.secretAccessKey = uri.password;
|
|
862
|
+
this.accessKeyId = decodeURIComponent(uri.username);
|
|
863
|
+
this.secretAccessKey = decodeURIComponent(uri.password);
|
|
819
864
|
if (["/", "", null].includes(uri.pathname)) {
|
|
820
865
|
this.bucket = "s3db";
|
|
821
866
|
this.keyPrefix = "";
|
|
822
867
|
} else {
|
|
823
868
|
let [, bucket, ...subpath] = uri.pathname.split("/");
|
|
824
|
-
this.bucket = bucket;
|
|
869
|
+
this.bucket = decodeURIComponent(bucket);
|
|
825
870
|
this.keyPrefix = [...subpath || []].join("/");
|
|
826
871
|
}
|
|
827
872
|
}
|
|
@@ -8830,18 +8875,83 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
8830
8875
|
const DEFAULT_BEHAVIOR = "user-management";
|
|
8831
8876
|
|
|
8832
8877
|
class Resource extends EventEmitter {
|
|
8833
|
-
|
|
8834
|
-
|
|
8835
|
-
|
|
8836
|
-
|
|
8837
|
-
|
|
8838
|
-
|
|
8839
|
-
|
|
8840
|
-
|
|
8841
|
-
|
|
8842
|
-
|
|
8843
|
-
|
|
8878
|
+
/**
|
|
8879
|
+
* Create a new Resource instance
|
|
8880
|
+
* @param {Object} config - Resource configuration
|
|
8881
|
+
* @param {string} config.name - Resource name
|
|
8882
|
+
* @param {Object} config.client - S3 client instance
|
|
8883
|
+
* @param {string} [config.version='v0'] - Resource version
|
|
8884
|
+
* @param {Object} [config.attributes={}] - Resource attributes schema
|
|
8885
|
+
* @param {string} [config.behavior='user-management'] - Resource behavior strategy
|
|
8886
|
+
* @param {string} [config.passphrase='secret'] - Encryption passphrase
|
|
8887
|
+
* @param {number} [config.parallelism=10] - Parallelism for bulk operations
|
|
8888
|
+
* @param {Array} [config.observers=[]] - Observer instances
|
|
8889
|
+
* @param {boolean} [config.cache=false] - Enable caching
|
|
8890
|
+
* @param {boolean} [config.autoDecrypt=true] - Auto-decrypt secret fields
|
|
8891
|
+
* @param {boolean} [config.timestamps=false] - Enable automatic timestamps
|
|
8892
|
+
* @param {Object} [config.partitions={}] - Partition definitions
|
|
8893
|
+
* @param {boolean} [config.paranoid=true] - Security flag for dangerous operations
|
|
8894
|
+
* @param {boolean} [config.allNestedObjectsOptional=false] - Make nested objects optional
|
|
8895
|
+
* @param {Object} [config.hooks={}] - Custom hooks
|
|
8896
|
+
* @param {Object} [config.options={}] - Additional options
|
|
8897
|
+
* @example
|
|
8898
|
+
* const users = new Resource({
|
|
8899
|
+
* name: 'users',
|
|
8900
|
+
* client: s3Client,
|
|
8901
|
+
* attributes: {
|
|
8902
|
+
* name: 'string|required',
|
|
8903
|
+
* email: 'string|required',
|
|
8904
|
+
* password: 'secret|required'
|
|
8905
|
+
* },
|
|
8906
|
+
* behavior: 'user-management',
|
|
8907
|
+
* passphrase: 'my-secret-key',
|
|
8908
|
+
* timestamps: true,
|
|
8909
|
+
* partitions: {
|
|
8910
|
+
* byRegion: {
|
|
8911
|
+
* fields: { region: 'string' }
|
|
8912
|
+
* }
|
|
8913
|
+
* },
|
|
8914
|
+
* hooks: {
|
|
8915
|
+
* preInsert: [async (data) => {
|
|
8916
|
+
* console.log('Pre-insert hook:', data);
|
|
8917
|
+
* return data;
|
|
8918
|
+
* }]
|
|
8919
|
+
* }
|
|
8920
|
+
* });
|
|
8921
|
+
*/
|
|
8922
|
+
constructor(config) {
|
|
8844
8923
|
super();
|
|
8924
|
+
const validation = validateResourceConfig(config);
|
|
8925
|
+
if (!validation.isValid) {
|
|
8926
|
+
throw new Error(`Invalid Resource configuration:
|
|
8927
|
+
${validation.errors.join("\n")}`);
|
|
8928
|
+
}
|
|
8929
|
+
const {
|
|
8930
|
+
name,
|
|
8931
|
+
client,
|
|
8932
|
+
version = "1",
|
|
8933
|
+
attributes = {},
|
|
8934
|
+
behavior = DEFAULT_BEHAVIOR,
|
|
8935
|
+
passphrase = "secret",
|
|
8936
|
+
parallelism = 10,
|
|
8937
|
+
observers = [],
|
|
8938
|
+
cache = false,
|
|
8939
|
+
autoDecrypt = true,
|
|
8940
|
+
timestamps = false,
|
|
8941
|
+
partitions = {},
|
|
8942
|
+
paranoid = true,
|
|
8943
|
+
allNestedObjectsOptional = true,
|
|
8944
|
+
hooks = {},
|
|
8945
|
+
options = {}
|
|
8946
|
+
} = config;
|
|
8947
|
+
const mergedOptions = {
|
|
8948
|
+
cache: typeof options.cache === "boolean" ? options.cache : cache,
|
|
8949
|
+
autoDecrypt: typeof options.autoDecrypt === "boolean" ? options.autoDecrypt : autoDecrypt,
|
|
8950
|
+
timestamps: typeof options.timestamps === "boolean" ? options.timestamps : timestamps,
|
|
8951
|
+
paranoid: typeof options.paranoid === "boolean" ? options.paranoid : paranoid,
|
|
8952
|
+
allNestedObjectsOptional: typeof options.allNestedObjectsOptional === "boolean" ? options.allNestedObjectsOptional : allNestedObjectsOptional,
|
|
8953
|
+
partitions: options.partitions || partitions || {}
|
|
8954
|
+
};
|
|
8845
8955
|
this.name = name;
|
|
8846
8956
|
this.client = client;
|
|
8847
8957
|
this.version = version;
|
|
@@ -8849,15 +8959,14 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
8849
8959
|
this.observers = observers;
|
|
8850
8960
|
this.parallelism = parallelism;
|
|
8851
8961
|
this.passphrase = passphrase ?? "secret";
|
|
8852
|
-
this.
|
|
8853
|
-
cache:
|
|
8854
|
-
|
|
8855
|
-
|
|
8856
|
-
|
|
8857
|
-
|
|
8858
|
-
|
|
8859
|
-
allNestedObjectsOptional:
|
|
8860
|
-
...options
|
|
8962
|
+
this.config = {
|
|
8963
|
+
cache: mergedOptions.cache,
|
|
8964
|
+
hooks,
|
|
8965
|
+
paranoid: mergedOptions.paranoid,
|
|
8966
|
+
timestamps: mergedOptions.timestamps,
|
|
8967
|
+
partitions: mergedOptions.partitions,
|
|
8968
|
+
autoDecrypt: mergedOptions.autoDecrypt,
|
|
8969
|
+
allNestedObjectsOptional: mergedOptions.allNestedObjectsOptional
|
|
8861
8970
|
};
|
|
8862
8971
|
this.hooks = {
|
|
8863
8972
|
preInsert: [],
|
|
@@ -8868,18 +8977,21 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
8868
8977
|
afterDelete: []
|
|
8869
8978
|
};
|
|
8870
8979
|
this.attributes = attributes || {};
|
|
8871
|
-
if (
|
|
8980
|
+
if (this.config.timestamps) {
|
|
8872
8981
|
this.attributes.createdAt = "string|optional";
|
|
8873
8982
|
this.attributes.updatedAt = "string|optional";
|
|
8874
|
-
if (!this.
|
|
8875
|
-
this.
|
|
8983
|
+
if (!this.config.partitions) {
|
|
8984
|
+
this.config.partitions = {};
|
|
8985
|
+
}
|
|
8986
|
+
if (!this.config.partitions.byCreatedDate) {
|
|
8987
|
+
this.config.partitions.byCreatedDate = {
|
|
8876
8988
|
fields: {
|
|
8877
8989
|
createdAt: "date|maxlength:10"
|
|
8878
8990
|
}
|
|
8879
8991
|
};
|
|
8880
8992
|
}
|
|
8881
|
-
if (!this.
|
|
8882
|
-
this.
|
|
8993
|
+
if (!this.config.partitions.byUpdatedDate) {
|
|
8994
|
+
this.config.partitions.byUpdatedDate = {
|
|
8883
8995
|
fields: {
|
|
8884
8996
|
updatedAt: "date|maxlength:10"
|
|
8885
8997
|
}
|
|
@@ -8892,25 +9004,47 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
8892
9004
|
passphrase,
|
|
8893
9005
|
version: this.version,
|
|
8894
9006
|
options: {
|
|
8895
|
-
|
|
8896
|
-
allNestedObjectsOptional: this.
|
|
9007
|
+
autoDecrypt: this.config.autoDecrypt,
|
|
9008
|
+
allNestedObjectsOptional: this.config.allNestedObjectsOptional
|
|
8897
9009
|
}
|
|
8898
9010
|
});
|
|
8899
|
-
this.validatePartitions();
|
|
8900
9011
|
this.setupPartitionHooks();
|
|
8901
|
-
|
|
8902
|
-
|
|
9012
|
+
this.validatePartitions();
|
|
9013
|
+
if (hooks) {
|
|
9014
|
+
for (const [event, hooksArr] of Object.entries(hooks)) {
|
|
8903
9015
|
if (Array.isArray(hooksArr) && this.hooks[event]) {
|
|
8904
9016
|
for (const fn of hooksArr) {
|
|
8905
|
-
|
|
9017
|
+
if (typeof fn === "function") {
|
|
9018
|
+
this.hooks[event].push(fn.bind(this));
|
|
9019
|
+
}
|
|
8906
9020
|
}
|
|
8907
9021
|
}
|
|
8908
9022
|
}
|
|
8909
9023
|
}
|
|
8910
9024
|
}
|
|
9025
|
+
/**
|
|
9026
|
+
* Get resource options (for backward compatibility with tests)
|
|
9027
|
+
*/
|
|
9028
|
+
get options() {
|
|
9029
|
+
return {
|
|
9030
|
+
timestamps: this.config.timestamps,
|
|
9031
|
+
partitions: this.config.partitions || {},
|
|
9032
|
+
cache: this.config.cache,
|
|
9033
|
+
autoDecrypt: this.config.autoDecrypt,
|
|
9034
|
+
paranoid: this.config.paranoid,
|
|
9035
|
+
allNestedObjectsOptional: this.config.allNestedObjectsOptional
|
|
9036
|
+
};
|
|
9037
|
+
}
|
|
8911
9038
|
export() {
|
|
8912
9039
|
const exported = this.schema.export();
|
|
8913
9040
|
exported.behavior = this.behavior;
|
|
9041
|
+
exported.timestamps = this.config.timestamps;
|
|
9042
|
+
exported.partitions = this.config.partitions || {};
|
|
9043
|
+
exported.paranoid = this.config.paranoid;
|
|
9044
|
+
exported.allNestedObjectsOptional = this.config.allNestedObjectsOptional;
|
|
9045
|
+
exported.autoDecrypt = this.config.autoDecrypt;
|
|
9046
|
+
exported.cache = this.config.cache;
|
|
9047
|
+
exported.hooks = this.hooks;
|
|
8914
9048
|
return exported;
|
|
8915
9049
|
}
|
|
8916
9050
|
/**
|
|
@@ -8920,18 +9054,21 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
8920
9054
|
updateAttributes(newAttributes) {
|
|
8921
9055
|
const oldAttributes = this.attributes;
|
|
8922
9056
|
this.attributes = newAttributes;
|
|
8923
|
-
if (this.
|
|
9057
|
+
if (this.config.timestamps) {
|
|
8924
9058
|
newAttributes.createdAt = "string|optional";
|
|
8925
9059
|
newAttributes.updatedAt = "string|optional";
|
|
8926
|
-
if (!this.
|
|
8927
|
-
this.
|
|
9060
|
+
if (!this.config.partitions) {
|
|
9061
|
+
this.config.partitions = {};
|
|
9062
|
+
}
|
|
9063
|
+
if (!this.config.partitions.byCreatedDate) {
|
|
9064
|
+
this.config.partitions.byCreatedDate = {
|
|
8928
9065
|
fields: {
|
|
8929
9066
|
createdAt: "date|maxlength:10"
|
|
8930
9067
|
}
|
|
8931
9068
|
};
|
|
8932
9069
|
}
|
|
8933
|
-
if (!this.
|
|
8934
|
-
this.
|
|
9070
|
+
if (!this.config.partitions.byUpdatedDate) {
|
|
9071
|
+
this.config.partitions.byUpdatedDate = {
|
|
8935
9072
|
fields: {
|
|
8936
9073
|
updatedAt: "date|maxlength:10"
|
|
8937
9074
|
}
|
|
@@ -8943,10 +9080,13 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
8943
9080
|
attributes: newAttributes,
|
|
8944
9081
|
passphrase: this.passphrase,
|
|
8945
9082
|
version: this.version,
|
|
8946
|
-
options:
|
|
9083
|
+
options: {
|
|
9084
|
+
autoDecrypt: this.config.autoDecrypt,
|
|
9085
|
+
allNestedObjectsOptional: this.config.allNestedObjectsOptional
|
|
9086
|
+
}
|
|
8947
9087
|
});
|
|
8948
|
-
this.validatePartitions();
|
|
8949
9088
|
this.setupPartitionHooks();
|
|
9089
|
+
this.validatePartitions();
|
|
8950
9090
|
return { oldAttributes, newAttributes };
|
|
8951
9091
|
}
|
|
8952
9092
|
/**
|
|
@@ -8977,15 +9117,24 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
8977
9117
|
* Setup automatic partition hooks
|
|
8978
9118
|
*/
|
|
8979
9119
|
setupPartitionHooks() {
|
|
8980
|
-
|
|
8981
|
-
|
|
9120
|
+
if (!this.config.partitions) {
|
|
9121
|
+
return;
|
|
9122
|
+
}
|
|
9123
|
+
const partitions = this.config.partitions;
|
|
9124
|
+
if (Object.keys(partitions).length === 0) {
|
|
8982
9125
|
return;
|
|
8983
9126
|
}
|
|
8984
|
-
this.
|
|
9127
|
+
if (!this.hooks.afterInsert) {
|
|
9128
|
+
this.hooks.afterInsert = [];
|
|
9129
|
+
}
|
|
9130
|
+
this.hooks.afterInsert.push(async (data) => {
|
|
8985
9131
|
await this.createPartitionReferences(data);
|
|
8986
9132
|
return data;
|
|
8987
9133
|
});
|
|
8988
|
-
this.
|
|
9134
|
+
if (!this.hooks.afterDelete) {
|
|
9135
|
+
this.hooks.afterDelete = [];
|
|
9136
|
+
}
|
|
9137
|
+
this.hooks.afterDelete.push(async (data) => {
|
|
8989
9138
|
await this.deletePartitionReferences(data);
|
|
8990
9139
|
return data;
|
|
8991
9140
|
});
|
|
@@ -9010,8 +9159,11 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9010
9159
|
* @throws {Error} If partition fields don't exist in current schema
|
|
9011
9160
|
*/
|
|
9012
9161
|
validatePartitions() {
|
|
9013
|
-
|
|
9014
|
-
|
|
9162
|
+
if (!this.config.partitions) {
|
|
9163
|
+
return;
|
|
9164
|
+
}
|
|
9165
|
+
const partitions = this.config.partitions;
|
|
9166
|
+
if (Object.keys(partitions).length === 0) {
|
|
9015
9167
|
return;
|
|
9016
9168
|
}
|
|
9017
9169
|
const currentAttributes = Object.keys(this.attributes || {});
|
|
@@ -9118,10 +9270,10 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9118
9270
|
* // Returns: null
|
|
9119
9271
|
*/
|
|
9120
9272
|
getPartitionKey({ partitionName, id, data }) {
|
|
9121
|
-
|
|
9122
|
-
if (!partition) {
|
|
9273
|
+
if (!this.config.partitions || !this.config.partitions[partitionName]) {
|
|
9123
9274
|
throw new Error(`Partition '${partitionName}' not found`);
|
|
9124
9275
|
}
|
|
9276
|
+
const partition = this.config.partitions[partitionName];
|
|
9125
9277
|
const partitionSegments = [];
|
|
9126
9278
|
const sortedFields = Object.entries(partition.fields).sort(([a], [b]) => a.localeCompare(b));
|
|
9127
9279
|
for (const [fieldName, rule] of sortedFields) {
|
|
@@ -9177,6 +9329,12 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9177
9329
|
* name: 'Jane Smith',
|
|
9178
9330
|
* email: 'jane@example.com'
|
|
9179
9331
|
* });
|
|
9332
|
+
*
|
|
9333
|
+
* // Insert with auto-generated password for secret field
|
|
9334
|
+
* const user = await resource.insert({
|
|
9335
|
+
* name: 'John Doe',
|
|
9336
|
+
* email: 'john@example.com',
|
|
9337
|
+
* });
|
|
9180
9338
|
*/
|
|
9181
9339
|
async insert({ id, ...attributes }) {
|
|
9182
9340
|
if (this.options.timestamps) {
|
|
@@ -9261,6 +9419,58 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9261
9419
|
this.emit("get", data);
|
|
9262
9420
|
return data;
|
|
9263
9421
|
} catch (error) {
|
|
9422
|
+
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError") || error.originalError?.message?.includes("Cipher job failed")) {
|
|
9423
|
+
try {
|
|
9424
|
+
console.warn(`Decryption failed for resource ${id}, attempting to get raw metadata`);
|
|
9425
|
+
const request = await this.client.headObject(key);
|
|
9426
|
+
const objectVersion = this.extractVersionFromKey(key) || this.version;
|
|
9427
|
+
const tempSchema = new Schema({
|
|
9428
|
+
name: this.name,
|
|
9429
|
+
attributes: this.attributes,
|
|
9430
|
+
passphrase: this.passphrase,
|
|
9431
|
+
version: objectVersion,
|
|
9432
|
+
options: {
|
|
9433
|
+
...this.config,
|
|
9434
|
+
autoDecrypt: false,
|
|
9435
|
+
// Disable decryption
|
|
9436
|
+
autoEncrypt: false
|
|
9437
|
+
// Disable encryption
|
|
9438
|
+
}
|
|
9439
|
+
});
|
|
9440
|
+
let metadata = await tempSchema.unmapper(request.Metadata);
|
|
9441
|
+
const behaviorImpl = getBehavior(this.behavior);
|
|
9442
|
+
let body = "";
|
|
9443
|
+
if (request.ContentLength > 0) {
|
|
9444
|
+
try {
|
|
9445
|
+
const fullObject = await this.client.getObject(key);
|
|
9446
|
+
body = await streamToString(fullObject.Body);
|
|
9447
|
+
} catch (bodyError) {
|
|
9448
|
+
console.warn(`Failed to read body for resource ${id}:`, bodyError.message);
|
|
9449
|
+
body = "";
|
|
9450
|
+
}
|
|
9451
|
+
}
|
|
9452
|
+
const { metadata: processedMetadata } = await behaviorImpl.handleGet({
|
|
9453
|
+
resource: this,
|
|
9454
|
+
metadata,
|
|
9455
|
+
body
|
|
9456
|
+
});
|
|
9457
|
+
let data = processedMetadata;
|
|
9458
|
+
data.id = id;
|
|
9459
|
+
data._contentLength = request.ContentLength;
|
|
9460
|
+
data._lastModified = request.LastModified;
|
|
9461
|
+
data._hasContent = request.ContentLength > 0;
|
|
9462
|
+
data._mimeType = request.ContentType || null;
|
|
9463
|
+
data._version = objectVersion;
|
|
9464
|
+
data._decryptionFailed = true;
|
|
9465
|
+
if (request.VersionId) data._versionId = request.VersionId;
|
|
9466
|
+
if (request.Expiration) data._expiresAt = request.Expiration;
|
|
9467
|
+
data._definitionHash = this.getDefinitionHash();
|
|
9468
|
+
this.emit("get", data);
|
|
9469
|
+
return data;
|
|
9470
|
+
} catch (fallbackError) {
|
|
9471
|
+
console.error(`Fallback attempt also failed for resource ${id}:`, fallbackError.message);
|
|
9472
|
+
}
|
|
9473
|
+
}
|
|
9264
9474
|
const enhancedError = new Error(`Failed to get resource with id '${id}': ${error.message}`);
|
|
9265
9475
|
enhancedError.originalError = error;
|
|
9266
9476
|
enhancedError.resourceId = id;
|
|
@@ -9307,7 +9517,7 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9307
9517
|
*/
|
|
9308
9518
|
async update(id, attributes) {
|
|
9309
9519
|
const live = await this.get(id);
|
|
9310
|
-
if (this.
|
|
9520
|
+
if (this.config.timestamps) {
|
|
9311
9521
|
attributes.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9312
9522
|
}
|
|
9313
9523
|
const preProcessedData = await this.executeHooks("preUpdate", attributes);
|
|
@@ -9429,7 +9639,7 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9429
9639
|
async count({ partition = null, partitionValues = {} } = {}) {
|
|
9430
9640
|
let prefix;
|
|
9431
9641
|
if (partition && Object.keys(partitionValues).length > 0) {
|
|
9432
|
-
const partitionDef = this.
|
|
9642
|
+
const partitionDef = this.config.partitions[partition];
|
|
9433
9643
|
if (!partitionDef) {
|
|
9434
9644
|
throw new Error(`Partition '${partition}' not found`);
|
|
9435
9645
|
}
|
|
@@ -9514,9 +9724,9 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9514
9724
|
return results;
|
|
9515
9725
|
}
|
|
9516
9726
|
async deleteAll() {
|
|
9517
|
-
if (this.
|
|
9727
|
+
if (this.config.paranoid !== false) {
|
|
9518
9728
|
throw new Error(
|
|
9519
|
-
`deleteAll() is a dangerous operation and requires paranoid: false option. Current paranoid setting: ${this.
|
|
9729
|
+
`deleteAll() is a dangerous operation and requires paranoid: false option. Current paranoid setting: ${this.config.paranoid}`
|
|
9520
9730
|
);
|
|
9521
9731
|
}
|
|
9522
9732
|
const prefix = `resource=${this.name}/v=${this.version}`;
|
|
@@ -9533,9 +9743,9 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9533
9743
|
* @returns {Promise<Object>} Deletion report
|
|
9534
9744
|
*/
|
|
9535
9745
|
async deleteAllData() {
|
|
9536
|
-
if (this.
|
|
9746
|
+
if (this.config.paranoid !== false) {
|
|
9537
9747
|
throw new Error(
|
|
9538
|
-
`deleteAllData() is a dangerous operation and requires paranoid: false option. Current paranoid setting: ${this.
|
|
9748
|
+
`deleteAllData() is a dangerous operation and requires paranoid: false option. Current paranoid setting: ${this.config.paranoid}`
|
|
9539
9749
|
);
|
|
9540
9750
|
}
|
|
9541
9751
|
const prefix = `resource=${this.name}`;
|
|
@@ -9578,10 +9788,10 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9578
9788
|
async listIds({ partition = null, partitionValues = {}, limit, offset = 0 } = {}) {
|
|
9579
9789
|
let prefix;
|
|
9580
9790
|
if (partition && Object.keys(partitionValues).length > 0) {
|
|
9581
|
-
|
|
9582
|
-
if (!partitionDef) {
|
|
9791
|
+
if (!this.config.partitions || !this.config.partitions[partition]) {
|
|
9583
9792
|
throw new Error(`Partition '${partition}' not found`);
|
|
9584
9793
|
}
|
|
9794
|
+
const partitionDef = this.config.partitions[partition];
|
|
9585
9795
|
const partitionSegments = [];
|
|
9586
9796
|
const sortedFields = Object.entries(partitionDef.fields).sort(([a], [b]) => a.localeCompare(b));
|
|
9587
9797
|
for (const [fieldName, rule] of sortedFields) {
|
|
@@ -9650,16 +9860,32 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9650
9860
|
if (limit) {
|
|
9651
9861
|
filteredIds2 = filteredIds2.slice(0, limit);
|
|
9652
9862
|
}
|
|
9653
|
-
const { results: results2 } = await promisePool.PromisePool.for(filteredIds2).withConcurrency(this.parallelism).
|
|
9654
|
-
|
|
9863
|
+
const { results: results2, errors: errors2 } = await promisePool.PromisePool.for(filteredIds2).withConcurrency(this.parallelism).handleError(async (error, id) => {
|
|
9864
|
+
console.warn(`Failed to get resource ${id}:`, error.message);
|
|
9865
|
+
return null;
|
|
9866
|
+
}).process(async (id) => {
|
|
9867
|
+
try {
|
|
9868
|
+
return await this.get(id);
|
|
9869
|
+
} catch (error) {
|
|
9870
|
+
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9871
|
+
console.warn(`Decryption failed for ${id}, returning basic info`);
|
|
9872
|
+
return {
|
|
9873
|
+
id,
|
|
9874
|
+
_decryptionFailed: true,
|
|
9875
|
+
_error: error.message
|
|
9876
|
+
};
|
|
9877
|
+
}
|
|
9878
|
+
throw error;
|
|
9879
|
+
}
|
|
9655
9880
|
});
|
|
9656
|
-
|
|
9657
|
-
|
|
9881
|
+
const validResults2 = results2.filter((item) => item !== null);
|
|
9882
|
+
this.emit("list", { partition, partitionValues, count: validResults2.length, errors: errors2.length });
|
|
9883
|
+
return validResults2;
|
|
9658
9884
|
}
|
|
9659
|
-
|
|
9660
|
-
if (!partitionDef) {
|
|
9885
|
+
if (!this.config.partitions || !this.config.partitions[partition]) {
|
|
9661
9886
|
throw new Error(`Partition '${partition}' not found`);
|
|
9662
9887
|
}
|
|
9888
|
+
const partitionDef = this.config.partitions[partition];
|
|
9663
9889
|
const partitionSegments = [];
|
|
9664
9890
|
const sortedFields = Object.entries(partitionDef.fields).sort(([a], [b]) => a.localeCompare(b));
|
|
9665
9891
|
for (const [fieldName, rule] of sortedFields) {
|
|
@@ -9685,11 +9911,29 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9685
9911
|
if (limit) {
|
|
9686
9912
|
filteredIds = filteredIds.slice(0, limit);
|
|
9687
9913
|
}
|
|
9688
|
-
const { results } = await promisePool.PromisePool.for(filteredIds).withConcurrency(this.parallelism).
|
|
9689
|
-
|
|
9914
|
+
const { results, errors } = await promisePool.PromisePool.for(filteredIds).withConcurrency(this.parallelism).handleError(async (error, id) => {
|
|
9915
|
+
console.warn(`Failed to get partition resource ${id}:`, error.message);
|
|
9916
|
+
return null;
|
|
9917
|
+
}).process(async (id) => {
|
|
9918
|
+
try {
|
|
9919
|
+
return await this.getFromPartition({ id, partitionName: partition, partitionValues });
|
|
9920
|
+
} catch (error) {
|
|
9921
|
+
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9922
|
+
console.warn(`Decryption failed for partition resource ${id}, returning basic info`);
|
|
9923
|
+
return {
|
|
9924
|
+
id,
|
|
9925
|
+
_partition: partition,
|
|
9926
|
+
_partitionValues: partitionValues,
|
|
9927
|
+
_decryptionFailed: true,
|
|
9928
|
+
_error: error.message
|
|
9929
|
+
};
|
|
9930
|
+
}
|
|
9931
|
+
throw error;
|
|
9932
|
+
}
|
|
9690
9933
|
});
|
|
9691
|
-
|
|
9692
|
-
|
|
9934
|
+
const validResults = results.filter((item) => item !== null);
|
|
9935
|
+
this.emit("list", { partition, partitionValues, count: validResults.length, errors: errors.length });
|
|
9936
|
+
return validResults;
|
|
9693
9937
|
}
|
|
9694
9938
|
/**
|
|
9695
9939
|
* Get multiple resources by their IDs
|
|
@@ -9700,11 +9944,30 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9700
9944
|
* users.forEach(user => console.log(user.name));
|
|
9701
9945
|
*/
|
|
9702
9946
|
async getMany(ids) {
|
|
9703
|
-
const { results } = await promisePool.PromisePool.for(ids).withConcurrency(this.client.parallelism).
|
|
9947
|
+
const { results, errors } = await promisePool.PromisePool.for(ids).withConcurrency(this.client.parallelism).handleError(async (error, id) => {
|
|
9948
|
+
console.warn(`Failed to get resource ${id}:`, error.message);
|
|
9949
|
+
return {
|
|
9950
|
+
id,
|
|
9951
|
+
_error: error.message,
|
|
9952
|
+
_decryptionFailed: error.message.includes("Cipher job failed") || error.message.includes("OperationError")
|
|
9953
|
+
};
|
|
9954
|
+
}).process(async (id) => {
|
|
9704
9955
|
this.emit("id", id);
|
|
9705
|
-
|
|
9706
|
-
|
|
9707
|
-
|
|
9956
|
+
try {
|
|
9957
|
+
const data = await this.get(id);
|
|
9958
|
+
this.emit("data", data);
|
|
9959
|
+
return data;
|
|
9960
|
+
} catch (error) {
|
|
9961
|
+
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9962
|
+
console.warn(`Decryption failed for ${id}, returning basic info`);
|
|
9963
|
+
return {
|
|
9964
|
+
id,
|
|
9965
|
+
_decryptionFailed: true,
|
|
9966
|
+
_error: error.message
|
|
9967
|
+
};
|
|
9968
|
+
}
|
|
9969
|
+
throw error;
|
|
9970
|
+
}
|
|
9708
9971
|
});
|
|
9709
9972
|
this.emit("getMany", ids.length);
|
|
9710
9973
|
return results;
|
|
@@ -9719,9 +9982,28 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9719
9982
|
async getAll() {
|
|
9720
9983
|
let ids = await this.listIds();
|
|
9721
9984
|
if (ids.length === 0) return [];
|
|
9722
|
-
const { results } = await promisePool.PromisePool.for(ids).withConcurrency(this.client.parallelism).
|
|
9723
|
-
|
|
9724
|
-
return
|
|
9985
|
+
const { results, errors } = await promisePool.PromisePool.for(ids).withConcurrency(this.client.parallelism).handleError(async (error, id) => {
|
|
9986
|
+
console.warn(`Failed to get resource ${id}:`, error.message);
|
|
9987
|
+
return {
|
|
9988
|
+
id,
|
|
9989
|
+
_error: error.message,
|
|
9990
|
+
_decryptionFailed: error.message.includes("Cipher job failed") || error.message.includes("OperationError")
|
|
9991
|
+
};
|
|
9992
|
+
}).process(async (id) => {
|
|
9993
|
+
try {
|
|
9994
|
+
const data = await this.get(id);
|
|
9995
|
+
return data;
|
|
9996
|
+
} catch (error) {
|
|
9997
|
+
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9998
|
+
console.warn(`Decryption failed for ${id}, returning basic info`);
|
|
9999
|
+
return {
|
|
10000
|
+
id,
|
|
10001
|
+
_decryptionFailed: true,
|
|
10002
|
+
_error: error.message
|
|
10003
|
+
};
|
|
10004
|
+
}
|
|
10005
|
+
throw error;
|
|
10006
|
+
}
|
|
9725
10007
|
});
|
|
9726
10008
|
this.emit("getAll", results.length);
|
|
9727
10009
|
return results;
|
|
@@ -9908,16 +10190,14 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9908
10190
|
}
|
|
9909
10191
|
/**
|
|
9910
10192
|
* Generate definition hash for this resource
|
|
9911
|
-
* @returns {string} SHA256 hash of the
|
|
10193
|
+
* @returns {string} SHA256 hash of the resource definition (name + attributes)
|
|
9912
10194
|
*/
|
|
9913
10195
|
getDefinitionHash() {
|
|
9914
|
-
const
|
|
9915
|
-
|
|
9916
|
-
|
|
9917
|
-
|
|
9918
|
-
|
|
9919
|
-
}
|
|
9920
|
-
const stableString = jsonStableStringify(stableAttributes);
|
|
10196
|
+
const definition = {
|
|
10197
|
+
attributes: this.attributes,
|
|
10198
|
+
behavior: this.behavior
|
|
10199
|
+
};
|
|
10200
|
+
const stableString = jsonStableStringify(definition);
|
|
9921
10201
|
return `sha256:${crypto.createHash("sha256").update(stableString).digest("hex")}`;
|
|
9922
10202
|
}
|
|
9923
10203
|
/**
|
|
@@ -9936,14 +10216,34 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9936
10216
|
* @returns {Object} Schema object for the version
|
|
9937
10217
|
*/
|
|
9938
10218
|
async getSchemaForVersion(version) {
|
|
9939
|
-
|
|
10219
|
+
if (version === this.version) {
|
|
10220
|
+
return this.schema;
|
|
10221
|
+
}
|
|
10222
|
+
try {
|
|
10223
|
+
const compatibleSchema = new Schema({
|
|
10224
|
+
name: this.name,
|
|
10225
|
+
attributes: this.attributes,
|
|
10226
|
+
passphrase: this.passphrase,
|
|
10227
|
+
version,
|
|
10228
|
+
options: {
|
|
10229
|
+
...this.config,
|
|
10230
|
+
// For older versions, be more lenient with decryption
|
|
10231
|
+
autoDecrypt: true,
|
|
10232
|
+
autoEncrypt: true
|
|
10233
|
+
}
|
|
10234
|
+
});
|
|
10235
|
+
return compatibleSchema;
|
|
10236
|
+
} catch (error) {
|
|
10237
|
+
console.warn(`Failed to create compatible schema for version ${version}, using current schema:`, error.message);
|
|
10238
|
+
return this.schema;
|
|
10239
|
+
}
|
|
9940
10240
|
}
|
|
9941
10241
|
/**
|
|
9942
10242
|
* Create partition references after insert
|
|
9943
10243
|
* @param {Object} data - Inserted object data
|
|
9944
10244
|
*/
|
|
9945
10245
|
async createPartitionReferences(data) {
|
|
9946
|
-
const partitions = this.
|
|
10246
|
+
const partitions = this.config.partitions;
|
|
9947
10247
|
if (!partitions || Object.keys(partitions).length === 0) {
|
|
9948
10248
|
return;
|
|
9949
10249
|
}
|
|
@@ -9974,7 +10274,7 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
9974
10274
|
* @param {Object} data - Deleted object data
|
|
9975
10275
|
*/
|
|
9976
10276
|
async deletePartitionReferences(data) {
|
|
9977
|
-
const partitions = this.
|
|
10277
|
+
const partitions = this.config.partitions;
|
|
9978
10278
|
if (!partitions || Object.keys(partitions).length === 0) {
|
|
9979
10279
|
return;
|
|
9980
10280
|
}
|
|
@@ -10066,7 +10366,7 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
10066
10366
|
* @param {Object} data - Updated object data
|
|
10067
10367
|
*/
|
|
10068
10368
|
async updatePartitionReferences(data) {
|
|
10069
|
-
const partitions = this.
|
|
10369
|
+
const partitions = this.config.partitions;
|
|
10070
10370
|
if (!partitions || Object.keys(partitions).length === 0) {
|
|
10071
10371
|
return;
|
|
10072
10372
|
}
|
|
@@ -10122,10 +10422,10 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
10122
10422
|
* });
|
|
10123
10423
|
*/
|
|
10124
10424
|
async getFromPartition({ id, partitionName, partitionValues = {} }) {
|
|
10125
|
-
|
|
10126
|
-
if (!partition) {
|
|
10425
|
+
if (!this.config.partitions || !this.config.partitions[partitionName]) {
|
|
10127
10426
|
throw new Error(`Partition '${partitionName}' not found`);
|
|
10128
10427
|
}
|
|
10428
|
+
const partition = this.config.partitions[partitionName];
|
|
10129
10429
|
const partitionSegments = [];
|
|
10130
10430
|
const sortedFields = Object.entries(partition.fields).sort(([a], [b]) => a.localeCompare(b));
|
|
10131
10431
|
for (const [fieldName, rule] of sortedFields) {
|
|
@@ -10173,6 +10473,98 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
10173
10473
|
return data;
|
|
10174
10474
|
}
|
|
10175
10475
|
}
|
|
10476
|
+
function validateResourceConfig(config) {
|
|
10477
|
+
const errors = [];
|
|
10478
|
+
if (!config.name) {
|
|
10479
|
+
errors.push("Resource 'name' is required");
|
|
10480
|
+
} else if (typeof config.name !== "string") {
|
|
10481
|
+
errors.push("Resource 'name' must be a string");
|
|
10482
|
+
} else if (config.name.trim() === "") {
|
|
10483
|
+
errors.push("Resource 'name' cannot be empty");
|
|
10484
|
+
}
|
|
10485
|
+
if (!config.client) {
|
|
10486
|
+
errors.push("S3 'client' is required");
|
|
10487
|
+
}
|
|
10488
|
+
if (!config.attributes) {
|
|
10489
|
+
errors.push("Resource 'attributes' are required");
|
|
10490
|
+
} else if (typeof config.attributes !== "object" || Array.isArray(config.attributes)) {
|
|
10491
|
+
errors.push("Resource 'attributes' must be an object");
|
|
10492
|
+
} else if (Object.keys(config.attributes).length === 0) {
|
|
10493
|
+
errors.push("Resource 'attributes' cannot be empty");
|
|
10494
|
+
}
|
|
10495
|
+
if (config.version !== void 0 && typeof config.version !== "string") {
|
|
10496
|
+
errors.push("Resource 'version' must be a string");
|
|
10497
|
+
}
|
|
10498
|
+
if (config.behavior !== void 0 && typeof config.behavior !== "string") {
|
|
10499
|
+
errors.push("Resource 'behavior' must be a string");
|
|
10500
|
+
}
|
|
10501
|
+
if (config.passphrase !== void 0 && typeof config.passphrase !== "string") {
|
|
10502
|
+
errors.push("Resource 'passphrase' must be a string");
|
|
10503
|
+
}
|
|
10504
|
+
if (config.parallelism !== void 0) {
|
|
10505
|
+
if (typeof config.parallelism !== "number" || !Number.isInteger(config.parallelism)) {
|
|
10506
|
+
errors.push("Resource 'parallelism' must be an integer");
|
|
10507
|
+
} else if (config.parallelism < 1) {
|
|
10508
|
+
errors.push("Resource 'parallelism' must be greater than 0");
|
|
10509
|
+
}
|
|
10510
|
+
}
|
|
10511
|
+
if (config.observers !== void 0 && !Array.isArray(config.observers)) {
|
|
10512
|
+
errors.push("Resource 'observers' must be an array");
|
|
10513
|
+
}
|
|
10514
|
+
const booleanFields = ["cache", "autoDecrypt", "timestamps", "paranoid", "allNestedObjectsOptional"];
|
|
10515
|
+
for (const field of booleanFields) {
|
|
10516
|
+
if (config[field] !== void 0 && typeof config[field] !== "boolean") {
|
|
10517
|
+
errors.push(`Resource '${field}' must be a boolean`);
|
|
10518
|
+
}
|
|
10519
|
+
}
|
|
10520
|
+
if (config.partitions !== void 0) {
|
|
10521
|
+
if (typeof config.partitions !== "object" || Array.isArray(config.partitions)) {
|
|
10522
|
+
errors.push("Resource 'partitions' must be an object");
|
|
10523
|
+
} else {
|
|
10524
|
+
for (const [partitionName, partitionDef] of Object.entries(config.partitions)) {
|
|
10525
|
+
if (typeof partitionDef !== "object" || Array.isArray(partitionDef)) {
|
|
10526
|
+
errors.push(`Partition '${partitionName}' must be an object`);
|
|
10527
|
+
} else if (!partitionDef.fields) {
|
|
10528
|
+
errors.push(`Partition '${partitionName}' must have a 'fields' property`);
|
|
10529
|
+
} else if (typeof partitionDef.fields !== "object" || Array.isArray(partitionDef.fields)) {
|
|
10530
|
+
errors.push(`Partition '${partitionName}.fields' must be an object`);
|
|
10531
|
+
} else {
|
|
10532
|
+
for (const [fieldName, fieldType] of Object.entries(partitionDef.fields)) {
|
|
10533
|
+
if (typeof fieldType !== "string") {
|
|
10534
|
+
errors.push(`Partition '${partitionName}.fields.${fieldName}' must be a string`);
|
|
10535
|
+
}
|
|
10536
|
+
}
|
|
10537
|
+
}
|
|
10538
|
+
}
|
|
10539
|
+
}
|
|
10540
|
+
}
|
|
10541
|
+
if (config.hooks !== void 0) {
|
|
10542
|
+
if (typeof config.hooks !== "object" || Array.isArray(config.hooks)) {
|
|
10543
|
+
errors.push("Resource 'hooks' must be an object");
|
|
10544
|
+
} else {
|
|
10545
|
+
const validHookEvents = ["preInsert", "afterInsert", "preUpdate", "afterUpdate", "preDelete", "afterDelete"];
|
|
10546
|
+
for (const [event, hooksArr] of Object.entries(config.hooks)) {
|
|
10547
|
+
if (!validHookEvents.includes(event)) {
|
|
10548
|
+
errors.push(`Invalid hook event '${event}'. Valid events: ${validHookEvents.join(", ")}`);
|
|
10549
|
+
} else if (!Array.isArray(hooksArr)) {
|
|
10550
|
+
errors.push(`Resource 'hooks.${event}' must be an array`);
|
|
10551
|
+
} else {
|
|
10552
|
+
for (let i = 0; i < hooksArr.length; i++) {
|
|
10553
|
+
const hook = hooksArr[i];
|
|
10554
|
+
if (typeof hook !== "function") {
|
|
10555
|
+
if (typeof hook === "string") continue;
|
|
10556
|
+
continue;
|
|
10557
|
+
}
|
|
10558
|
+
}
|
|
10559
|
+
}
|
|
10560
|
+
}
|
|
10561
|
+
}
|
|
10562
|
+
}
|
|
10563
|
+
return {
|
|
10564
|
+
isValid: errors.length === 0,
|
|
10565
|
+
errors
|
|
10566
|
+
};
|
|
10567
|
+
}
|
|
10176
10568
|
|
|
10177
10569
|
class Database extends EventEmitter {
|
|
10178
10570
|
constructor(options) {
|
|
@@ -10180,7 +10572,7 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
10180
10572
|
this.version = "1";
|
|
10181
10573
|
this.s3dbVersion = (() => {
|
|
10182
10574
|
try {
|
|
10183
|
-
return true ? "4.1.
|
|
10575
|
+
return true ? "4.1.10" : "latest";
|
|
10184
10576
|
} catch (e) {
|
|
10185
10577
|
return "latest";
|
|
10186
10578
|
}
|
|
@@ -10193,10 +10585,21 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
10193
10585
|
this.plugins = options.plugins || [];
|
|
10194
10586
|
this.cache = options.cache;
|
|
10195
10587
|
this.passphrase = options.passphrase || "secret";
|
|
10588
|
+
let connectionString = options.connectionString;
|
|
10589
|
+
if (!connectionString && (options.bucket || options.accessKeyId || options.secretAccessKey)) {
|
|
10590
|
+
connectionString = ConnectionString.buildFromParams({
|
|
10591
|
+
bucket: options.bucket,
|
|
10592
|
+
region: options.region,
|
|
10593
|
+
accessKeyId: options.accessKeyId,
|
|
10594
|
+
secretAccessKey: options.secretAccessKey,
|
|
10595
|
+
endpoint: options.endpoint,
|
|
10596
|
+
forcePathStyle: options.forcePathStyle
|
|
10597
|
+
});
|
|
10598
|
+
}
|
|
10196
10599
|
this.client = options.client || new Client({
|
|
10197
10600
|
verbose: this.verbose,
|
|
10198
10601
|
parallelism: this.parallelism,
|
|
10199
|
-
connectionString
|
|
10602
|
+
connectionString
|
|
10200
10603
|
});
|
|
10201
10604
|
this.bucket = this.client.bucket;
|
|
10202
10605
|
this.keyPrefix = this.client.keyPrefix;
|
|
@@ -10221,15 +10624,18 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
10221
10624
|
name,
|
|
10222
10625
|
client: this.client,
|
|
10223
10626
|
version: currentVersion,
|
|
10224
|
-
options: {
|
|
10225
|
-
...versionData.options,
|
|
10226
|
-
partitions: resourceMetadata.partitions || versionData.options?.partitions || {}
|
|
10227
|
-
},
|
|
10228
10627
|
attributes: versionData.attributes,
|
|
10229
10628
|
behavior: versionData.behavior || "user-management",
|
|
10230
10629
|
parallelism: this.parallelism,
|
|
10231
10630
|
passphrase: this.passphrase,
|
|
10232
|
-
observers: [this]
|
|
10631
|
+
observers: [this],
|
|
10632
|
+
cache: this.cache,
|
|
10633
|
+
timestamps: versionData.options?.timestamps || false,
|
|
10634
|
+
partitions: resourceMetadata.partitions || versionData.options?.partitions || {},
|
|
10635
|
+
paranoid: versionData.options?.paranoid !== false,
|
|
10636
|
+
allNestedObjectsOptional: versionData.options?.allNestedObjectsOptional || false,
|
|
10637
|
+
autoDecrypt: versionData.options?.autoDecrypt !== false,
|
|
10638
|
+
hooks: {}
|
|
10233
10639
|
});
|
|
10234
10640
|
}
|
|
10235
10641
|
}
|
|
@@ -10298,7 +10704,7 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
10298
10704
|
generateDefinitionHash(definition, behavior = void 0) {
|
|
10299
10705
|
const attributes = definition.attributes;
|
|
10300
10706
|
const stableAttributes = { ...attributes };
|
|
10301
|
-
if (definition.
|
|
10707
|
+
if (definition.timestamps) {
|
|
10302
10708
|
delete stableAttributes.createdAt;
|
|
10303
10709
|
delete stableAttributes.updatedAt;
|
|
10304
10710
|
}
|
|
@@ -10360,14 +10766,21 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
10360
10766
|
}
|
|
10361
10767
|
metadata.resources[name] = {
|
|
10362
10768
|
currentVersion: version,
|
|
10363
|
-
partitions:
|
|
10769
|
+
partitions: resource.config.partitions || {},
|
|
10364
10770
|
versions: {
|
|
10365
10771
|
...existingResource?.versions,
|
|
10366
10772
|
// Preserve previous versions
|
|
10367
10773
|
[version]: {
|
|
10368
10774
|
hash: definitionHash,
|
|
10369
10775
|
attributes: resourceDef.attributes,
|
|
10370
|
-
options:
|
|
10776
|
+
options: {
|
|
10777
|
+
timestamps: resource.config.timestamps,
|
|
10778
|
+
partitions: resource.config.partitions,
|
|
10779
|
+
paranoid: resource.config.paranoid,
|
|
10780
|
+
allNestedObjectsOptional: resource.config.allNestedObjectsOptional,
|
|
10781
|
+
autoDecrypt: resource.config.autoDecrypt,
|
|
10782
|
+
cache: resource.config.cache
|
|
10783
|
+
},
|
|
10371
10784
|
behavior: resourceDef.behavior || "user-management",
|
|
10372
10785
|
createdAt: isNewVersion ? (/* @__PURE__ */ new Date()).toISOString() : existingVersionData?.createdAt
|
|
10373
10786
|
}
|
|
@@ -10420,10 +10833,9 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
10420
10833
|
observers: [],
|
|
10421
10834
|
client: this.client,
|
|
10422
10835
|
version: "temp",
|
|
10423
|
-
|
|
10424
|
-
|
|
10425
|
-
|
|
10426
|
-
}
|
|
10836
|
+
passphrase: this.passphrase,
|
|
10837
|
+
cache: this.cache,
|
|
10838
|
+
...options
|
|
10427
10839
|
});
|
|
10428
10840
|
const newHash = this.generateDefinitionHash(tempResource.export(), behavior);
|
|
10429
10841
|
const existingHash = this.generateDefinitionHash(this.resources[name].export(), this.resources[name].behavior);
|
|
@@ -10463,7 +10875,7 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
10463
10875
|
async createResource({ name, attributes, options = {}, behavior = "user-management" }) {
|
|
10464
10876
|
if (this.resources[name]) {
|
|
10465
10877
|
const existingResource = this.resources[name];
|
|
10466
|
-
Object.assign(existingResource.
|
|
10878
|
+
Object.assign(existingResource.config, {
|
|
10467
10879
|
cache: this.cache,
|
|
10468
10880
|
...options
|
|
10469
10881
|
});
|
|
@@ -10490,10 +10902,9 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
10490
10902
|
observers: [this],
|
|
10491
10903
|
client: this.client,
|
|
10492
10904
|
version,
|
|
10493
|
-
|
|
10494
|
-
|
|
10495
|
-
|
|
10496
|
-
}
|
|
10905
|
+
passphrase: this.passphrase,
|
|
10906
|
+
cache: this.cache,
|
|
10907
|
+
...options
|
|
10497
10908
|
});
|
|
10498
10909
|
this.resources[name] = resource;
|
|
10499
10910
|
await this.uploadMetadataFile();
|
|
@@ -17195,6 +17606,7 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
17195
17606
|
}
|
|
17196
17607
|
}
|
|
17197
17608
|
|
|
17609
|
+
exports.AuthenticationError = AuthenticationError;
|
|
17198
17610
|
exports.BaseError = BaseError;
|
|
17199
17611
|
exports.Cache = Cache;
|
|
17200
17612
|
exports.CachePlugin = CachePlugin;
|
|
@@ -17202,6 +17614,8 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
17202
17614
|
exports.ConnectionString = ConnectionString;
|
|
17203
17615
|
exports.CostsPlugin = CostsPlugin;
|
|
17204
17616
|
exports.Database = Database;
|
|
17617
|
+
exports.DatabaseError = DatabaseError;
|
|
17618
|
+
exports.EncryptionError = EncryptionError;
|
|
17205
17619
|
exports.ErrorMap = ErrorMap;
|
|
17206
17620
|
exports.InvalidResourceItem = InvalidResourceItem;
|
|
17207
17621
|
exports.MemoryCache = MemoryCache;
|
|
@@ -17209,24 +17623,34 @@ ${JSON.stringify(validation, null, 2)}`
|
|
|
17209
17623
|
exports.NoSuchBucket = NoSuchBucket;
|
|
17210
17624
|
exports.NoSuchKey = NoSuchKey;
|
|
17211
17625
|
exports.NotFound = NotFound;
|
|
17626
|
+
exports.PermissionError = PermissionError;
|
|
17212
17627
|
exports.Plugin = Plugin;
|
|
17213
17628
|
exports.PluginObject = PluginObject;
|
|
17214
17629
|
exports.ResourceIdsPageReader = ResourceIdsPageReader;
|
|
17215
17630
|
exports.ResourceIdsReader = ResourceIdsReader;
|
|
17631
|
+
exports.ResourceNotFound = ResourceNotFound;
|
|
17632
|
+
exports.ResourceNotFoundError = ResourceNotFound;
|
|
17216
17633
|
exports.ResourceReader = ResourceReader;
|
|
17217
17634
|
exports.ResourceWriter = ResourceWriter;
|
|
17218
17635
|
exports.S3Cache = S3Cache;
|
|
17636
|
+
exports.S3DB = S3db;
|
|
17637
|
+
exports.S3DBError = S3DBError;
|
|
17219
17638
|
exports.S3_DEFAULT_ENDPOINT = S3_DEFAULT_ENDPOINT;
|
|
17220
17639
|
exports.S3_DEFAULT_REGION = S3_DEFAULT_REGION;
|
|
17221
17640
|
exports.S3db = S3db;
|
|
17641
|
+
exports.S3dbError = S3DBError;
|
|
17222
17642
|
exports.UnknownError = UnknownError;
|
|
17643
|
+
exports.ValidationError = ValidationError;
|
|
17223
17644
|
exports.Validator = Validator;
|
|
17224
17645
|
exports.ValidatorManager = ValidatorManager;
|
|
17225
17646
|
exports.decrypt = decrypt;
|
|
17647
|
+
exports.default = S3db;
|
|
17226
17648
|
exports.encrypt = encrypt;
|
|
17227
17649
|
exports.sha256 = sha256;
|
|
17228
17650
|
exports.streamToString = streamToString;
|
|
17229
17651
|
|
|
17652
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
17653
|
+
|
|
17230
17654
|
return exports;
|
|
17231
17655
|
|
|
17232
17656
|
})({}, nanoid, lodashEs, promisePool, clientS3, crypto, flat, FastestValidator, web);
|