s3db.js 4.1.13 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/s3db.cjs.js +442 -229
- package/dist/s3db.cjs.min.js +11 -11
- package/dist/s3db.es.js +429 -228
- package/dist/s3db.es.min.js +13 -13
- package/dist/s3db.iife.js +442 -229
- package/dist/s3db.iife.min.js +11 -11
- package/package.json +1 -1
package/dist/s3db.es.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* istanbul ignore file */
|
|
2
2
|
import { customAlphabet, urlAlphabet } from 'nanoid';
|
|
3
|
-
import { chunk, merge, isEmpty, invert, uniq, cloneDeep,
|
|
3
|
+
import { chunk, merge, isString as isString$1, isEmpty, invert, uniq, cloneDeep, get as get$1, set, isFunction as isFunction$1 } from 'lodash-es';
|
|
4
4
|
import { PromisePool } from '@supercharge/promise-pool';
|
|
5
5
|
import { S3Client, PutObjectCommand, GetObjectCommand, HeadObjectCommand, CopyObjectCommand, DeleteObjectCommand, DeleteObjectsCommand, ListObjectsV2Command } from '@aws-sdk/client-s3';
|
|
6
6
|
import { createHash } from 'crypto';
|
|
@@ -243,7 +243,7 @@ var substr = 'ab'.substr(-1) === 'b' ?
|
|
|
243
243
|
|
|
244
244
|
const idGenerator = customAlphabet(urlAlphabet, 22);
|
|
245
245
|
const passwordAlphabet = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789";
|
|
246
|
-
customAlphabet(passwordAlphabet, 12);
|
|
246
|
+
const passwordGenerator = customAlphabet(passwordAlphabet, 12);
|
|
247
247
|
|
|
248
248
|
var domain;
|
|
249
249
|
|
|
@@ -3446,10 +3446,31 @@ const ValidatorManager = new Proxy(Validator, {
|
|
|
3446
3446
|
}
|
|
3447
3447
|
});
|
|
3448
3448
|
|
|
3449
|
+
function toBase36(num) {
|
|
3450
|
+
return num.toString(36);
|
|
3451
|
+
}
|
|
3452
|
+
function generateBase36Mapping(keys) {
|
|
3453
|
+
const mapping = {};
|
|
3454
|
+
const reversedMapping = {};
|
|
3455
|
+
keys.forEach((key, index) => {
|
|
3456
|
+
const base36Key = toBase36(index);
|
|
3457
|
+
mapping[key] = base36Key;
|
|
3458
|
+
reversedMapping[base36Key] = key;
|
|
3459
|
+
});
|
|
3460
|
+
return { mapping, reversedMapping };
|
|
3461
|
+
}
|
|
3449
3462
|
const SchemaActions = {
|
|
3450
3463
|
trim: (value) => value.trim(),
|
|
3451
3464
|
encrypt: (value, { passphrase }) => encrypt(value, passphrase),
|
|
3452
|
-
decrypt: (value, { passphrase }) =>
|
|
3465
|
+
decrypt: async (value, { passphrase }) => {
|
|
3466
|
+
try {
|
|
3467
|
+
const raw = await decrypt(value, passphrase);
|
|
3468
|
+
return raw;
|
|
3469
|
+
} catch (error) {
|
|
3470
|
+
console.warn(`Schema decrypt error: ${error}`, error);
|
|
3471
|
+
return value;
|
|
3472
|
+
}
|
|
3473
|
+
},
|
|
3453
3474
|
toString: (value) => String(value),
|
|
3454
3475
|
fromArray: (value, { separator }) => {
|
|
3455
3476
|
if (value === null || value === void 0 || !Array.isArray(value)) {
|
|
@@ -3540,8 +3561,9 @@ class Schema {
|
|
|
3540
3561
|
const leafKeys = Object.keys(flatAttrs).filter((k) => !k.includes("$$"));
|
|
3541
3562
|
const objectKeys = this.extractObjectKeys(this.attributes);
|
|
3542
3563
|
const allKeys = [.../* @__PURE__ */ new Set([...leafKeys, ...objectKeys])];
|
|
3543
|
-
|
|
3544
|
-
this.map =
|
|
3564
|
+
const { mapping, reversedMapping } = generateBase36Mapping(allKeys);
|
|
3565
|
+
this.map = mapping;
|
|
3566
|
+
this.reversedMap = reversedMapping;
|
|
3545
3567
|
}
|
|
3546
3568
|
}
|
|
3547
3569
|
defaultOptions() {
|
|
@@ -8614,7 +8636,6 @@ function calculateUTF8Bytes(str) {
|
|
|
8614
8636
|
function calculateAttributeNamesSize(mappedObject) {
|
|
8615
8637
|
let totalSize = 0;
|
|
8616
8638
|
for (const key of Object.keys(mappedObject)) {
|
|
8617
|
-
if (key === "_v") continue;
|
|
8618
8639
|
totalSize += calculateUTF8Bytes(key);
|
|
8619
8640
|
}
|
|
8620
8641
|
return totalSize;
|
|
@@ -8658,6 +8679,30 @@ function calculateTotalSize(mappedObject) {
|
|
|
8658
8679
|
const namesSize = calculateAttributeNamesSize(mappedObject);
|
|
8659
8680
|
return valueTotal + namesSize;
|
|
8660
8681
|
}
|
|
8682
|
+
function getSizeBreakdown(mappedObject) {
|
|
8683
|
+
const valueSizes = calculateAttributeSizes(mappedObject);
|
|
8684
|
+
const namesSize = calculateAttributeNamesSize(mappedObject);
|
|
8685
|
+
const valueTotal = Object.values(valueSizes).reduce((sum, size) => sum + size, 0);
|
|
8686
|
+
const total = valueTotal + namesSize;
|
|
8687
|
+
const sortedAttributes = Object.entries(valueSizes).sort(([, a], [, b]) => b - a).map(([key, size]) => ({
|
|
8688
|
+
attribute: key,
|
|
8689
|
+
size,
|
|
8690
|
+
percentage: (size / total * 100).toFixed(2) + "%"
|
|
8691
|
+
}));
|
|
8692
|
+
return {
|
|
8693
|
+
total,
|
|
8694
|
+
valueSizes,
|
|
8695
|
+
namesSize,
|
|
8696
|
+
valueTotal,
|
|
8697
|
+
breakdown: sortedAttributes,
|
|
8698
|
+
// Add detailed breakdown including names
|
|
8699
|
+
detailedBreakdown: {
|
|
8700
|
+
values: valueTotal,
|
|
8701
|
+
names: namesSize,
|
|
8702
|
+
total
|
|
8703
|
+
}
|
|
8704
|
+
};
|
|
8705
|
+
}
|
|
8661
8706
|
|
|
8662
8707
|
const S3_METADATA_LIMIT_BYTES = 2048;
|
|
8663
8708
|
async function handleInsert$3({ resource, data, mappedData }) {
|
|
@@ -8878,6 +8923,7 @@ function getBehavior(behaviorName) {
|
|
|
8878
8923
|
}
|
|
8879
8924
|
return behavior;
|
|
8880
8925
|
}
|
|
8926
|
+
const AVAILABLE_BEHAVIORS = Object.keys(behaviors);
|
|
8881
8927
|
const DEFAULT_BEHAVIOR = "user-management";
|
|
8882
8928
|
|
|
8883
8929
|
class Resource extends EventEmitter {
|
|
@@ -8947,17 +8993,8 @@ ${validation.errors.join("\n")}`);
|
|
|
8947
8993
|
partitions = {},
|
|
8948
8994
|
paranoid = true,
|
|
8949
8995
|
allNestedObjectsOptional = true,
|
|
8950
|
-
hooks = {}
|
|
8951
|
-
options = {}
|
|
8996
|
+
hooks = {}
|
|
8952
8997
|
} = 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
|
-
};
|
|
8961
8998
|
this.name = name;
|
|
8962
8999
|
this.client = client;
|
|
8963
9000
|
this.version = version;
|
|
@@ -8966,13 +9003,13 @@ ${validation.errors.join("\n")}`);
|
|
|
8966
9003
|
this.parallelism = parallelism;
|
|
8967
9004
|
this.passphrase = passphrase ?? "secret";
|
|
8968
9005
|
this.config = {
|
|
8969
|
-
cache
|
|
9006
|
+
cache,
|
|
8970
9007
|
hooks,
|
|
8971
|
-
paranoid
|
|
8972
|
-
timestamps
|
|
8973
|
-
partitions
|
|
8974
|
-
autoDecrypt
|
|
8975
|
-
allNestedObjectsOptional
|
|
9008
|
+
paranoid,
|
|
9009
|
+
timestamps,
|
|
9010
|
+
partitions,
|
|
9011
|
+
autoDecrypt,
|
|
9012
|
+
allNestedObjectsOptional
|
|
8976
9013
|
};
|
|
8977
9014
|
this.hooks = {
|
|
8978
9015
|
preInsert: [],
|
|
@@ -8983,39 +9020,7 @@ ${validation.errors.join("\n")}`);
|
|
|
8983
9020
|
afterDelete: []
|
|
8984
9021
|
};
|
|
8985
9022
|
this.attributes = attributes || {};
|
|
8986
|
-
|
|
8987
|
-
this.attributes.createdAt = "string|optional";
|
|
8988
|
-
this.attributes.updatedAt = "string|optional";
|
|
8989
|
-
if (!this.config.partitions) {
|
|
8990
|
-
this.config.partitions = {};
|
|
8991
|
-
}
|
|
8992
|
-
if (!this.config.partitions.byCreatedDate) {
|
|
8993
|
-
this.config.partitions.byCreatedDate = {
|
|
8994
|
-
fields: {
|
|
8995
|
-
createdAt: "date|maxlength:10"
|
|
8996
|
-
}
|
|
8997
|
-
};
|
|
8998
|
-
}
|
|
8999
|
-
if (!this.config.partitions.byUpdatedDate) {
|
|
9000
|
-
this.config.partitions.byUpdatedDate = {
|
|
9001
|
-
fields: {
|
|
9002
|
-
updatedAt: "date|maxlength:10"
|
|
9003
|
-
}
|
|
9004
|
-
};
|
|
9005
|
-
}
|
|
9006
|
-
}
|
|
9007
|
-
this.schema = new Schema({
|
|
9008
|
-
name,
|
|
9009
|
-
attributes: this.attributes,
|
|
9010
|
-
passphrase,
|
|
9011
|
-
version: this.version,
|
|
9012
|
-
options: {
|
|
9013
|
-
autoDecrypt: this.config.autoDecrypt,
|
|
9014
|
-
allNestedObjectsOptional: this.config.allNestedObjectsOptional
|
|
9015
|
-
}
|
|
9016
|
-
});
|
|
9017
|
-
this.setupPartitionHooks();
|
|
9018
|
-
this.validatePartitions();
|
|
9023
|
+
this.applyConfiguration();
|
|
9019
9024
|
if (hooks) {
|
|
9020
9025
|
for (const [event, hooksArr] of Object.entries(hooks)) {
|
|
9021
9026
|
if (Array.isArray(hooksArr) && this.hooks[event]) {
|
|
@@ -9054,15 +9059,17 @@ ${validation.errors.join("\n")}`);
|
|
|
9054
9059
|
return exported;
|
|
9055
9060
|
}
|
|
9056
9061
|
/**
|
|
9057
|
-
*
|
|
9058
|
-
*
|
|
9062
|
+
* Apply configuration settings (timestamps, partitions, hooks)
|
|
9063
|
+
* This method ensures that all configuration-dependent features are properly set up
|
|
9059
9064
|
*/
|
|
9060
|
-
|
|
9061
|
-
const oldAttributes = this.attributes;
|
|
9062
|
-
this.attributes = newAttributes;
|
|
9065
|
+
applyConfiguration() {
|
|
9063
9066
|
if (this.config.timestamps) {
|
|
9064
|
-
|
|
9065
|
-
|
|
9067
|
+
if (!this.attributes.createdAt) {
|
|
9068
|
+
this.attributes.createdAt = "string|optional";
|
|
9069
|
+
}
|
|
9070
|
+
if (!this.attributes.updatedAt) {
|
|
9071
|
+
this.attributes.updatedAt = "string|optional";
|
|
9072
|
+
}
|
|
9066
9073
|
if (!this.config.partitions) {
|
|
9067
9074
|
this.config.partitions = {};
|
|
9068
9075
|
}
|
|
@@ -9081,9 +9088,10 @@ ${validation.errors.join("\n")}`);
|
|
|
9081
9088
|
};
|
|
9082
9089
|
}
|
|
9083
9090
|
}
|
|
9091
|
+
this.setupPartitionHooks();
|
|
9084
9092
|
this.schema = new Schema({
|
|
9085
9093
|
name: this.name,
|
|
9086
|
-
attributes:
|
|
9094
|
+
attributes: this.attributes,
|
|
9087
9095
|
passphrase: this.passphrase,
|
|
9088
9096
|
version: this.version,
|
|
9089
9097
|
options: {
|
|
@@ -9091,8 +9099,16 @@ ${validation.errors.join("\n")}`);
|
|
|
9091
9099
|
allNestedObjectsOptional: this.config.allNestedObjectsOptional
|
|
9092
9100
|
}
|
|
9093
9101
|
});
|
|
9094
|
-
this.setupPartitionHooks();
|
|
9095
9102
|
this.validatePartitions();
|
|
9103
|
+
}
|
|
9104
|
+
/**
|
|
9105
|
+
* Update resource attributes and rebuild schema
|
|
9106
|
+
* @param {Object} newAttributes - New attributes definition
|
|
9107
|
+
*/
|
|
9108
|
+
updateAttributes(newAttributes) {
|
|
9109
|
+
const oldAttributes = this.attributes;
|
|
9110
|
+
this.attributes = newAttributes;
|
|
9111
|
+
this.applyConfiguration();
|
|
9096
9112
|
return { oldAttributes, newAttributes };
|
|
9097
9113
|
}
|
|
9098
9114
|
/**
|
|
@@ -9180,7 +9196,7 @@ ${validation.errors.join("\n")}`);
|
|
|
9180
9196
|
for (const fieldName of Object.keys(partitionDef.fields)) {
|
|
9181
9197
|
if (!this.fieldExistsInAttributes(fieldName)) {
|
|
9182
9198
|
throw new Error(
|
|
9183
|
-
`Partition '${partitionName}' uses field '${fieldName}' which does not exist in resource
|
|
9199
|
+
`Partition '${partitionName}' uses field '${fieldName}' which does not exist in resource attributes. Available fields: ${currentAttributes.join(", ")}.`
|
|
9184
9200
|
);
|
|
9185
9201
|
}
|
|
9186
9202
|
}
|
|
@@ -9293,7 +9309,11 @@ ${validation.errors.join("\n")}`);
|
|
|
9293
9309
|
if (partitionSegments.length === 0) {
|
|
9294
9310
|
return null;
|
|
9295
9311
|
}
|
|
9296
|
-
|
|
9312
|
+
const finalId = id || data?.id;
|
|
9313
|
+
if (!finalId) {
|
|
9314
|
+
return null;
|
|
9315
|
+
}
|
|
9316
|
+
return join(`resource=${this.name}`, `partition=${partitionName}`, ...partitionSegments, `id=${finalId}`);
|
|
9297
9317
|
}
|
|
9298
9318
|
/**
|
|
9299
9319
|
* Get nested field value from data object using dot notation
|
|
@@ -9522,12 +9542,12 @@ ${validation.errors.join("\n")}`);
|
|
|
9522
9542
|
* console.log(updatedUser.updatedAt); // ISO timestamp
|
|
9523
9543
|
*/
|
|
9524
9544
|
async update(id, attributes) {
|
|
9525
|
-
const
|
|
9545
|
+
const originalData = await this.get(id);
|
|
9526
9546
|
if (this.config.timestamps) {
|
|
9527
9547
|
attributes.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9528
9548
|
}
|
|
9529
9549
|
const preProcessedData = await this.executeHooks("preUpdate", attributes);
|
|
9530
|
-
const attrs = merge(
|
|
9550
|
+
const attrs = merge(originalData, preProcessedData);
|
|
9531
9551
|
delete attrs.id;
|
|
9532
9552
|
const { isValid, errors, data: validated } = await this.validate(attrs);
|
|
9533
9553
|
if (!isValid) {
|
|
@@ -9538,6 +9558,9 @@ ${validation.errors.join("\n")}`);
|
|
|
9538
9558
|
validation: errors
|
|
9539
9559
|
});
|
|
9540
9560
|
}
|
|
9561
|
+
const oldData = { ...originalData, id };
|
|
9562
|
+
const newData = { ...validated, id };
|
|
9563
|
+
await this.handlePartitionReferenceUpdates(oldData, newData);
|
|
9541
9564
|
const mappedData = await this.schema.mapper(validated);
|
|
9542
9565
|
const behaviorImpl = getBehavior(this.behavior);
|
|
9543
9566
|
const { mappedData: processedMetadata, body } = await behaviorImpl.handleUpdate({
|
|
@@ -9573,7 +9596,6 @@ ${validation.errors.join("\n")}`);
|
|
|
9573
9596
|
});
|
|
9574
9597
|
validated.id = id;
|
|
9575
9598
|
await this.executeHooks("afterUpdate", validated);
|
|
9576
|
-
await this.updatePartitionReferences(validated);
|
|
9577
9599
|
this.emit("update", preProcessedData, validated);
|
|
9578
9600
|
return validated;
|
|
9579
9601
|
}
|
|
@@ -9860,23 +9882,104 @@ ${validation.errors.join("\n")}`);
|
|
|
9860
9882
|
* });
|
|
9861
9883
|
*/
|
|
9862
9884
|
async list({ partition = null, partitionValues = {}, limit, offset = 0 } = {}) {
|
|
9863
|
-
|
|
9864
|
-
|
|
9865
|
-
|
|
9885
|
+
try {
|
|
9886
|
+
if (!partition) {
|
|
9887
|
+
let ids2 = [];
|
|
9888
|
+
try {
|
|
9889
|
+
ids2 = await this.listIds({ partition, partitionValues });
|
|
9890
|
+
} catch (listIdsError) {
|
|
9891
|
+
console.warn(`Failed to get list IDs:`, listIdsError.message);
|
|
9892
|
+
return [];
|
|
9893
|
+
}
|
|
9894
|
+
let filteredIds2 = ids2.slice(offset);
|
|
9895
|
+
if (limit) {
|
|
9896
|
+
filteredIds2 = filteredIds2.slice(0, limit);
|
|
9897
|
+
}
|
|
9898
|
+
const { results: results2, errors: errors2 } = await PromisePool.for(filteredIds2).withConcurrency(this.parallelism).handleError(async (error, id) => {
|
|
9899
|
+
console.warn(`Failed to get resource ${id}:`, error.message);
|
|
9900
|
+
return null;
|
|
9901
|
+
}).process(async (id) => {
|
|
9902
|
+
try {
|
|
9903
|
+
return await this.get(id);
|
|
9904
|
+
} catch (error) {
|
|
9905
|
+
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9906
|
+
console.warn(`Decryption failed for ${id}, returning basic info`);
|
|
9907
|
+
return {
|
|
9908
|
+
id,
|
|
9909
|
+
_decryptionFailed: true,
|
|
9910
|
+
_error: error.message
|
|
9911
|
+
};
|
|
9912
|
+
}
|
|
9913
|
+
throw error;
|
|
9914
|
+
}
|
|
9915
|
+
});
|
|
9916
|
+
const validResults2 = results2.filter((item) => item !== null);
|
|
9917
|
+
this.emit("list", { partition, partitionValues, count: validResults2.length, errors: errors2.length });
|
|
9918
|
+
return validResults2;
|
|
9919
|
+
}
|
|
9920
|
+
if (!this.config.partitions || !this.config.partitions[partition]) {
|
|
9921
|
+
console.warn(`Partition '${partition}' not found in resource '${this.name}'`);
|
|
9922
|
+
this.emit("list", { partition, partitionValues, count: 0, errors: 0 });
|
|
9923
|
+
return [];
|
|
9924
|
+
}
|
|
9925
|
+
const partitionDef = this.config.partitions[partition];
|
|
9926
|
+
const partitionSegments = [];
|
|
9927
|
+
const sortedFields = Object.entries(partitionDef.fields).sort(([a], [b]) => a.localeCompare(b));
|
|
9928
|
+
for (const [fieldName, rule] of sortedFields) {
|
|
9929
|
+
const value = partitionValues[fieldName];
|
|
9930
|
+
if (value !== void 0 && value !== null) {
|
|
9931
|
+
const transformedValue = this.applyPartitionRule(value, rule);
|
|
9932
|
+
partitionSegments.push(`${fieldName}=${transformedValue}`);
|
|
9933
|
+
}
|
|
9934
|
+
}
|
|
9935
|
+
let prefix;
|
|
9936
|
+
if (partitionSegments.length > 0) {
|
|
9937
|
+
prefix = `resource=${this.name}/partition=${partition}/${partitionSegments.join("/")}`;
|
|
9938
|
+
} else {
|
|
9939
|
+
prefix = `resource=${this.name}/partition=${partition}`;
|
|
9940
|
+
}
|
|
9941
|
+
let keys = [];
|
|
9942
|
+
try {
|
|
9943
|
+
keys = await this.client.getAllKeys({ prefix });
|
|
9944
|
+
} catch (getKeysError) {
|
|
9945
|
+
console.warn(`Failed to get partition keys:`, getKeysError.message);
|
|
9946
|
+
return [];
|
|
9947
|
+
}
|
|
9948
|
+
const ids = keys.map((key) => {
|
|
9949
|
+
const parts = key.split("/");
|
|
9950
|
+
const idPart = parts.find((part) => part.startsWith("id="));
|
|
9951
|
+
return idPart ? idPart.replace("id=", "") : null;
|
|
9952
|
+
}).filter(Boolean);
|
|
9953
|
+
let filteredIds = ids.slice(offset);
|
|
9866
9954
|
if (limit) {
|
|
9867
|
-
|
|
9955
|
+
filteredIds = filteredIds.slice(0, limit);
|
|
9868
9956
|
}
|
|
9869
|
-
const { results
|
|
9870
|
-
console.warn(`Failed to get resource ${id}:`, error.message);
|
|
9957
|
+
const { results, errors } = await PromisePool.for(filteredIds).withConcurrency(this.parallelism).handleError(async (error, id) => {
|
|
9958
|
+
console.warn(`Failed to get partition resource ${id}:`, error.message);
|
|
9871
9959
|
return null;
|
|
9872
9960
|
}).process(async (id) => {
|
|
9873
9961
|
try {
|
|
9874
|
-
|
|
9962
|
+
const keyForId = keys.find((key) => key.includes(`id=${id}`));
|
|
9963
|
+
if (!keyForId) {
|
|
9964
|
+
throw new Error(`Partition key not found for ID ${id}`);
|
|
9965
|
+
}
|
|
9966
|
+
const keyParts = keyForId.split("/");
|
|
9967
|
+
const actualPartitionValues = {};
|
|
9968
|
+
for (const [fieldName, rule] of sortedFields) {
|
|
9969
|
+
const fieldPart = keyParts.find((part) => part.startsWith(`${fieldName}=`));
|
|
9970
|
+
if (fieldPart) {
|
|
9971
|
+
const value = fieldPart.replace(`${fieldName}=`, "");
|
|
9972
|
+
actualPartitionValues[fieldName] = value;
|
|
9973
|
+
}
|
|
9974
|
+
}
|
|
9975
|
+
return await this.getFromPartition({ id, partitionName: partition, partitionValues: actualPartitionValues });
|
|
9875
9976
|
} catch (error) {
|
|
9876
9977
|
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9877
|
-
console.warn(`Decryption failed for ${id}, returning basic info`);
|
|
9978
|
+
console.warn(`Decryption failed for partition resource ${id}, returning basic info`);
|
|
9878
9979
|
return {
|
|
9879
9980
|
id,
|
|
9981
|
+
_partition: partition,
|
|
9982
|
+
_partitionValues: partitionValues,
|
|
9880
9983
|
_decryptionFailed: true,
|
|
9881
9984
|
_error: error.message
|
|
9882
9985
|
};
|
|
@@ -9884,62 +9987,19 @@ ${validation.errors.join("\n")}`);
|
|
|
9884
9987
|
throw error;
|
|
9885
9988
|
}
|
|
9886
9989
|
});
|
|
9887
|
-
const
|
|
9888
|
-
this.emit("list", { partition, partitionValues, count:
|
|
9889
|
-
return
|
|
9890
|
-
}
|
|
9891
|
-
|
|
9892
|
-
|
|
9893
|
-
|
|
9894
|
-
|
|
9895
|
-
const partitionSegments = [];
|
|
9896
|
-
const sortedFields = Object.entries(partitionDef.fields).sort(([a], [b]) => a.localeCompare(b));
|
|
9897
|
-
for (const [fieldName, rule] of sortedFields) {
|
|
9898
|
-
const value = partitionValues[fieldName];
|
|
9899
|
-
if (value !== void 0 && value !== null) {
|
|
9900
|
-
const transformedValue = this.applyPartitionRule(value, rule);
|
|
9901
|
-
partitionSegments.push(`${fieldName}=${transformedValue}`);
|
|
9990
|
+
const validResults = results.filter((item) => item !== null);
|
|
9991
|
+
this.emit("list", { partition, partitionValues, count: validResults.length, errors: errors.length });
|
|
9992
|
+
return validResults;
|
|
9993
|
+
} catch (error) {
|
|
9994
|
+
if (error.message.includes("Partition '") && error.message.includes("' not found")) {
|
|
9995
|
+
console.warn(`Partition error in list method:`, error.message);
|
|
9996
|
+
this.emit("list", { partition, partitionValues, count: 0, errors: 1 });
|
|
9997
|
+
return [];
|
|
9902
9998
|
}
|
|
9999
|
+
console.error(`Critical error in list method:`, error.message);
|
|
10000
|
+
this.emit("list", { partition, partitionValues, count: 0, errors: 1 });
|
|
10001
|
+
return [];
|
|
9903
10002
|
}
|
|
9904
|
-
let prefix;
|
|
9905
|
-
if (partitionSegments.length > 0) {
|
|
9906
|
-
prefix = `resource=${this.name}/partition=${partition}/${partitionSegments.join("/")}`;
|
|
9907
|
-
} else {
|
|
9908
|
-
prefix = `resource=${this.name}/partition=${partition}`;
|
|
9909
|
-
}
|
|
9910
|
-
const keys = await this.client.getAllKeys({ prefix });
|
|
9911
|
-
const ids = keys.map((key) => {
|
|
9912
|
-
const parts = key.split("/");
|
|
9913
|
-
const idPart = parts.find((part) => part.startsWith("id="));
|
|
9914
|
-
return idPart ? idPart.replace("id=", "") : null;
|
|
9915
|
-
}).filter(Boolean);
|
|
9916
|
-
let filteredIds = ids.slice(offset);
|
|
9917
|
-
if (limit) {
|
|
9918
|
-
filteredIds = filteredIds.slice(0, limit);
|
|
9919
|
-
}
|
|
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
|
-
}
|
|
9939
|
-
});
|
|
9940
|
-
const validResults = results.filter((item) => item !== null);
|
|
9941
|
-
this.emit("list", { partition, partitionValues, count: validResults.length, errors: errors.length });
|
|
9942
|
-
return validResults;
|
|
9943
10003
|
}
|
|
9944
10004
|
/**
|
|
9945
10005
|
* Get multiple resources by their IDs
|
|
@@ -10046,36 +10106,67 @@ ${validation.errors.join("\n")}`);
|
|
|
10046
10106
|
* console.log(`Got ${fastPage.items.length} items`); // totalItems will be null
|
|
10047
10107
|
*/
|
|
10048
10108
|
async page({ offset = 0, size = 100, partition = null, partitionValues = {}, skipCount = false } = {}) {
|
|
10049
|
-
|
|
10050
|
-
|
|
10051
|
-
|
|
10052
|
-
|
|
10053
|
-
|
|
10054
|
-
|
|
10055
|
-
|
|
10056
|
-
|
|
10057
|
-
|
|
10058
|
-
|
|
10059
|
-
|
|
10060
|
-
|
|
10061
|
-
});
|
|
10062
|
-
const result = {
|
|
10063
|
-
items,
|
|
10064
|
-
totalItems,
|
|
10065
|
-
page,
|
|
10066
|
-
pageSize: size,
|
|
10067
|
-
totalPages,
|
|
10068
|
-
// Add additional metadata for debugging
|
|
10069
|
-
_debug: {
|
|
10070
|
-
requestedSize: size,
|
|
10071
|
-
requestedOffset: offset,
|
|
10072
|
-
actualItemsReturned: items.length,
|
|
10073
|
-
skipCount,
|
|
10074
|
-
hasTotalItems: totalItems !== null
|
|
10109
|
+
try {
|
|
10110
|
+
let totalItems = null;
|
|
10111
|
+
let totalPages = null;
|
|
10112
|
+
if (!skipCount) {
|
|
10113
|
+
try {
|
|
10114
|
+
totalItems = await this.count({ partition, partitionValues });
|
|
10115
|
+
totalPages = Math.ceil(totalItems / size);
|
|
10116
|
+
} catch (countError) {
|
|
10117
|
+
console.warn(`Failed to get count for page:`, countError.message);
|
|
10118
|
+
totalItems = null;
|
|
10119
|
+
totalPages = null;
|
|
10120
|
+
}
|
|
10075
10121
|
}
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10122
|
+
const page = Math.floor(offset / size);
|
|
10123
|
+
let items = [];
|
|
10124
|
+
try {
|
|
10125
|
+
items = await this.list({
|
|
10126
|
+
partition,
|
|
10127
|
+
partitionValues,
|
|
10128
|
+
limit: size,
|
|
10129
|
+
offset
|
|
10130
|
+
});
|
|
10131
|
+
} catch (listError) {
|
|
10132
|
+
console.warn(`Failed to get items for page:`, listError.message);
|
|
10133
|
+
items = [];
|
|
10134
|
+
}
|
|
10135
|
+
const result = {
|
|
10136
|
+
items,
|
|
10137
|
+
totalItems,
|
|
10138
|
+
page,
|
|
10139
|
+
pageSize: size,
|
|
10140
|
+
totalPages,
|
|
10141
|
+
// Add additional metadata for debugging
|
|
10142
|
+
_debug: {
|
|
10143
|
+
requestedSize: size,
|
|
10144
|
+
requestedOffset: offset,
|
|
10145
|
+
actualItemsReturned: items.length,
|
|
10146
|
+
skipCount,
|
|
10147
|
+
hasTotalItems: totalItems !== null
|
|
10148
|
+
}
|
|
10149
|
+
};
|
|
10150
|
+
this.emit("page", result);
|
|
10151
|
+
return result;
|
|
10152
|
+
} catch (error) {
|
|
10153
|
+
console.error(`Critical error in page method:`, error.message);
|
|
10154
|
+
return {
|
|
10155
|
+
items: [],
|
|
10156
|
+
totalItems: null,
|
|
10157
|
+
page: Math.floor(offset / size),
|
|
10158
|
+
pageSize: size,
|
|
10159
|
+
totalPages: null,
|
|
10160
|
+
_debug: {
|
|
10161
|
+
requestedSize: size,
|
|
10162
|
+
requestedOffset: offset,
|
|
10163
|
+
actualItemsReturned: 0,
|
|
10164
|
+
skipCount,
|
|
10165
|
+
hasTotalItems: false,
|
|
10166
|
+
error: error.message
|
|
10167
|
+
}
|
|
10168
|
+
};
|
|
10169
|
+
}
|
|
10079
10170
|
}
|
|
10080
10171
|
readable() {
|
|
10081
10172
|
const stream = new ResourceReader({ resource: this });
|
|
@@ -10368,7 +10459,118 @@ ${validation.errors.join("\n")}`);
|
|
|
10368
10459
|
return results.slice(0, limit);
|
|
10369
10460
|
}
|
|
10370
10461
|
/**
|
|
10371
|
-
*
|
|
10462
|
+
* Handle partition reference updates with change detection
|
|
10463
|
+
* @param {Object} oldData - Original object data before update
|
|
10464
|
+
* @param {Object} newData - Updated object data
|
|
10465
|
+
*/
|
|
10466
|
+
async handlePartitionReferenceUpdates(oldData, newData) {
|
|
10467
|
+
const partitions = this.config.partitions;
|
|
10468
|
+
if (!partitions || Object.keys(partitions).length === 0) {
|
|
10469
|
+
return;
|
|
10470
|
+
}
|
|
10471
|
+
for (const [partitionName, partition] of Object.entries(partitions)) {
|
|
10472
|
+
try {
|
|
10473
|
+
await this.handlePartitionReferenceUpdate(partitionName, partition, oldData, newData);
|
|
10474
|
+
} catch (error) {
|
|
10475
|
+
console.warn(`Failed to update partition references for ${partitionName}:`, error.message);
|
|
10476
|
+
}
|
|
10477
|
+
}
|
|
10478
|
+
const id = newData.id || oldData.id;
|
|
10479
|
+
for (const [partitionName, partition] of Object.entries(partitions)) {
|
|
10480
|
+
const prefix = `resource=${this.name}/partition=${partitionName}`;
|
|
10481
|
+
let allKeys = [];
|
|
10482
|
+
try {
|
|
10483
|
+
allKeys = await this.client.getAllKeys({ prefix });
|
|
10484
|
+
} catch (error) {
|
|
10485
|
+
console.warn(`Aggressive cleanup: could not list keys for partition ${partitionName}:`, error.message);
|
|
10486
|
+
continue;
|
|
10487
|
+
}
|
|
10488
|
+
const validKey = this.getPartitionKey({ partitionName, id, data: newData });
|
|
10489
|
+
for (const key of allKeys) {
|
|
10490
|
+
if (key.endsWith(`/id=${id}`) && key !== validKey) {
|
|
10491
|
+
try {
|
|
10492
|
+
await this.client.deleteObject(key);
|
|
10493
|
+
} catch (error) {
|
|
10494
|
+
console.warn(`Aggressive cleanup: could not delete stale partition key ${key}:`, error.message);
|
|
10495
|
+
}
|
|
10496
|
+
}
|
|
10497
|
+
}
|
|
10498
|
+
}
|
|
10499
|
+
}
|
|
10500
|
+
/**
|
|
10501
|
+
* Handle partition reference update for a specific partition
|
|
10502
|
+
* @param {string} partitionName - Name of the partition
|
|
10503
|
+
* @param {Object} partition - Partition definition
|
|
10504
|
+
* @param {Object} oldData - Original object data before update
|
|
10505
|
+
* @param {Object} newData - Updated object data
|
|
10506
|
+
*/
|
|
10507
|
+
async handlePartitionReferenceUpdate(partitionName, partition, oldData, newData) {
|
|
10508
|
+
const id = newData.id || oldData.id;
|
|
10509
|
+
const oldPartitionKey = this.getPartitionKey({ partitionName, id, data: oldData });
|
|
10510
|
+
const newPartitionKey = this.getPartitionKey({ partitionName, id, data: newData });
|
|
10511
|
+
if (oldPartitionKey !== newPartitionKey) {
|
|
10512
|
+
if (oldPartitionKey) {
|
|
10513
|
+
try {
|
|
10514
|
+
await this.client.deleteObject(oldPartitionKey);
|
|
10515
|
+
} catch (error) {
|
|
10516
|
+
console.warn(`Old partition object could not be deleted for ${partitionName}:`, error.message);
|
|
10517
|
+
}
|
|
10518
|
+
}
|
|
10519
|
+
if (newPartitionKey) {
|
|
10520
|
+
try {
|
|
10521
|
+
const mappedData = await this.schema.mapper(newData);
|
|
10522
|
+
if (mappedData.undefined !== void 0) delete mappedData.undefined;
|
|
10523
|
+
const behaviorImpl = getBehavior(this.behavior);
|
|
10524
|
+
const { mappedData: processedMetadata, body } = await behaviorImpl.handleUpdate({
|
|
10525
|
+
resource: this,
|
|
10526
|
+
id,
|
|
10527
|
+
data: newData,
|
|
10528
|
+
mappedData
|
|
10529
|
+
});
|
|
10530
|
+
if (processedMetadata.undefined !== void 0) delete processedMetadata.undefined;
|
|
10531
|
+
const partitionMetadata = {
|
|
10532
|
+
...processedMetadata,
|
|
10533
|
+
_version: this.version
|
|
10534
|
+
};
|
|
10535
|
+
if (partitionMetadata.undefined !== void 0) delete partitionMetadata.undefined;
|
|
10536
|
+
await this.client.putObject({
|
|
10537
|
+
key: newPartitionKey,
|
|
10538
|
+
metadata: partitionMetadata,
|
|
10539
|
+
body
|
|
10540
|
+
});
|
|
10541
|
+
} catch (error) {
|
|
10542
|
+
console.warn(`New partition object could not be created for ${partitionName}:`, error.message);
|
|
10543
|
+
}
|
|
10544
|
+
}
|
|
10545
|
+
} else if (newPartitionKey) {
|
|
10546
|
+
try {
|
|
10547
|
+
const mappedData = await this.schema.mapper(newData);
|
|
10548
|
+
if (mappedData.undefined !== void 0) delete mappedData.undefined;
|
|
10549
|
+
const behaviorImpl = getBehavior(this.behavior);
|
|
10550
|
+
const { mappedData: processedMetadata, body } = await behaviorImpl.handleUpdate({
|
|
10551
|
+
resource: this,
|
|
10552
|
+
id,
|
|
10553
|
+
data: newData,
|
|
10554
|
+
mappedData
|
|
10555
|
+
});
|
|
10556
|
+
if (processedMetadata.undefined !== void 0) delete processedMetadata.undefined;
|
|
10557
|
+
const partitionMetadata = {
|
|
10558
|
+
...processedMetadata,
|
|
10559
|
+
_version: this.version
|
|
10560
|
+
};
|
|
10561
|
+
if (partitionMetadata.undefined !== void 0) delete partitionMetadata.undefined;
|
|
10562
|
+
await this.client.putObject({
|
|
10563
|
+
key: newPartitionKey,
|
|
10564
|
+
metadata: partitionMetadata,
|
|
10565
|
+
body
|
|
10566
|
+
});
|
|
10567
|
+
} catch (error) {
|
|
10568
|
+
console.warn(`Partition object could not be updated for ${partitionName}:`, error.message);
|
|
10569
|
+
}
|
|
10570
|
+
}
|
|
10571
|
+
}
|
|
10572
|
+
/**
|
|
10573
|
+
* Update partition objects to keep them in sync (legacy method for backward compatibility)
|
|
10372
10574
|
* @param {Object} data - Updated object data
|
|
10373
10575
|
*/
|
|
10374
10576
|
async updatePartitionReferences(data) {
|
|
@@ -10377,6 +10579,10 @@ ${validation.errors.join("\n")}`);
|
|
|
10377
10579
|
return;
|
|
10378
10580
|
}
|
|
10379
10581
|
for (const [partitionName, partition] of Object.entries(partitions)) {
|
|
10582
|
+
if (!partition || !partition.fields || typeof partition.fields !== "object") {
|
|
10583
|
+
console.warn(`Skipping invalid partition '${partitionName}' in resource '${this.name}'`);
|
|
10584
|
+
continue;
|
|
10585
|
+
}
|
|
10380
10586
|
const partitionKey = this.getPartitionKey({ partitionName, id: data.id, data });
|
|
10381
10587
|
if (partitionKey) {
|
|
10382
10588
|
const mappedData = await this.schema.mapper(data);
|
|
@@ -10475,6 +10681,7 @@ ${validation.errors.join("\n")}`);
|
|
|
10475
10681
|
if (request.VersionId) data._versionId = request.VersionId;
|
|
10476
10682
|
if (request.Expiration) data._expiresAt = request.Expiration;
|
|
10477
10683
|
data._definitionHash = this.getDefinitionHash();
|
|
10684
|
+
if (data.undefined !== void 0) delete data.undefined;
|
|
10478
10685
|
this.emit("getFromPartition", data);
|
|
10479
10686
|
return data;
|
|
10480
10687
|
}
|
|
@@ -10578,7 +10785,7 @@ class Database extends EventEmitter {
|
|
|
10578
10785
|
this.version = "1";
|
|
10579
10786
|
this.s3dbVersion = (() => {
|
|
10580
10787
|
try {
|
|
10581
|
-
return true ? "
|
|
10788
|
+
return true ? "5.0.0" : "latest";
|
|
10582
10789
|
} catch (e) {
|
|
10583
10790
|
return "latest";
|
|
10584
10791
|
}
|
|
@@ -10593,14 +10800,24 @@ class Database extends EventEmitter {
|
|
|
10593
10800
|
this.passphrase = options.passphrase || "secret";
|
|
10594
10801
|
let connectionString = options.connectionString;
|
|
10595
10802
|
if (!connectionString && (options.bucket || options.accessKeyId || options.secretAccessKey)) {
|
|
10596
|
-
|
|
10597
|
-
|
|
10598
|
-
|
|
10599
|
-
accessKeyId
|
|
10600
|
-
secretAccessKey
|
|
10601
|
-
|
|
10602
|
-
forcePathStyle
|
|
10603
|
-
|
|
10803
|
+
const { bucket, region, accessKeyId, secretAccessKey, endpoint, forcePathStyle } = options;
|
|
10804
|
+
if (endpoint) {
|
|
10805
|
+
const url = new URL(endpoint);
|
|
10806
|
+
if (accessKeyId) url.username = encodeURIComponent(accessKeyId);
|
|
10807
|
+
if (secretAccessKey) url.password = encodeURIComponent(secretAccessKey);
|
|
10808
|
+
url.pathname = `/${bucket || "s3db"}`;
|
|
10809
|
+
if (forcePathStyle) {
|
|
10810
|
+
url.searchParams.set("forcePathStyle", "true");
|
|
10811
|
+
}
|
|
10812
|
+
connectionString = url.toString();
|
|
10813
|
+
} else if (accessKeyId && secretAccessKey) {
|
|
10814
|
+
const params = new URLSearchParams();
|
|
10815
|
+
params.set("region", region || "us-east-1");
|
|
10816
|
+
if (forcePathStyle) {
|
|
10817
|
+
params.set("forcePathStyle", "true");
|
|
10818
|
+
}
|
|
10819
|
+
connectionString = `s3://${encodeURIComponent(accessKeyId)}:${encodeURIComponent(secretAccessKey)}@${bucket || "s3db"}?${params.toString()}`;
|
|
10820
|
+
}
|
|
10604
10821
|
}
|
|
10605
10822
|
this.client = options.client || new Client({
|
|
10606
10823
|
verbose: this.verbose,
|
|
@@ -10636,12 +10853,12 @@ class Database extends EventEmitter {
|
|
|
10636
10853
|
passphrase: this.passphrase,
|
|
10637
10854
|
observers: [this],
|
|
10638
10855
|
cache: this.cache,
|
|
10639
|
-
timestamps: versionData.
|
|
10640
|
-
partitions: resourceMetadata.partitions || versionData.
|
|
10641
|
-
paranoid: versionData.
|
|
10642
|
-
allNestedObjectsOptional: versionData.
|
|
10643
|
-
autoDecrypt: versionData.
|
|
10644
|
-
hooks: versionData.
|
|
10856
|
+
timestamps: versionData.timestamps !== void 0 ? versionData.timestamps : false,
|
|
10857
|
+
partitions: resourceMetadata.partitions || versionData.partitions || {},
|
|
10858
|
+
paranoid: versionData.paranoid !== void 0 ? versionData.paranoid : true,
|
|
10859
|
+
allNestedObjectsOptional: versionData.allNestedObjectsOptional !== void 0 ? versionData.allNestedObjectsOptional : true,
|
|
10860
|
+
autoDecrypt: versionData.autoDecrypt !== void 0 ? versionData.autoDecrypt : true,
|
|
10861
|
+
hooks: versionData.hooks || {}
|
|
10645
10862
|
});
|
|
10646
10863
|
}
|
|
10647
10864
|
}
|
|
@@ -10779,16 +10996,14 @@ class Database extends EventEmitter {
|
|
|
10779
10996
|
[version]: {
|
|
10780
10997
|
hash: definitionHash,
|
|
10781
10998
|
attributes: resourceDef.attributes,
|
|
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
|
-
hooks: resourceDef.hooks || {}
|
|
10790
|
-
},
|
|
10791
10999
|
behavior: resourceDef.behavior || "user-management",
|
|
11000
|
+
timestamps: resource.config.timestamps,
|
|
11001
|
+
partitions: resource.config.partitions,
|
|
11002
|
+
paranoid: resource.config.paranoid,
|
|
11003
|
+
allNestedObjectsOptional: resource.config.allNestedObjectsOptional,
|
|
11004
|
+
autoDecrypt: resource.config.autoDecrypt,
|
|
11005
|
+
cache: resource.config.cache,
|
|
11006
|
+
hooks: resource.config.hooks,
|
|
10792
11007
|
createdAt: isNewVersion ? (/* @__PURE__ */ new Date()).toISOString() : existingVersionData?.createdAt
|
|
10793
11008
|
}
|
|
10794
11009
|
}
|
|
@@ -10823,73 +11038,58 @@ class Database extends EventEmitter {
|
|
|
10823
11038
|
}
|
|
10824
11039
|
/**
|
|
10825
11040
|
* Check if a resource exists with the same definition hash
|
|
10826
|
-
* @param {
|
|
10827
|
-
* @param {
|
|
10828
|
-
* @param {Object}
|
|
10829
|
-
* @param {string} behavior - Resource behavior
|
|
10830
|
-
* @
|
|
11041
|
+
* @param {Object} config - Resource configuration
|
|
11042
|
+
* @param {string} config.name - Resource name
|
|
11043
|
+
* @param {Object} config.attributes - Resource attributes
|
|
11044
|
+
* @param {string} [config.behavior] - Resource behavior
|
|
11045
|
+
* @param {Object} [config.options] - Resource options (deprecated, use root level parameters)
|
|
11046
|
+
* @returns {Object} Result with exists and hash information
|
|
10831
11047
|
*/
|
|
10832
|
-
resourceExistsWithSameHash({ name, attributes,
|
|
11048
|
+
resourceExistsWithSameHash({ name, attributes, behavior = "user-management", options = {} }) {
|
|
10833
11049
|
if (!this.resources[name]) {
|
|
10834
11050
|
return { exists: false, sameHash: false, hash: null };
|
|
10835
11051
|
}
|
|
10836
|
-
const
|
|
11052
|
+
const existingResource = this.resources[name];
|
|
11053
|
+
const existingHash = this.generateDefinitionHash(existingResource.export());
|
|
11054
|
+
const mockResource = new Resource({
|
|
10837
11055
|
name,
|
|
10838
11056
|
attributes,
|
|
10839
11057
|
behavior,
|
|
10840
|
-
observers: [],
|
|
10841
11058
|
client: this.client,
|
|
10842
|
-
version:
|
|
11059
|
+
version: existingResource.version,
|
|
10843
11060
|
passphrase: this.passphrase,
|
|
10844
|
-
cache: this.cache,
|
|
10845
11061
|
...options
|
|
10846
11062
|
});
|
|
10847
|
-
const newHash = this.generateDefinitionHash(
|
|
10848
|
-
const existingHash = this.generateDefinitionHash(this.resources[name].export(), this.resources[name].behavior);
|
|
11063
|
+
const newHash = this.generateDefinitionHash(mockResource.export());
|
|
10849
11064
|
return {
|
|
10850
11065
|
exists: true,
|
|
10851
|
-
sameHash:
|
|
11066
|
+
sameHash: existingHash === newHash,
|
|
10852
11067
|
hash: newHash,
|
|
10853
11068
|
existingHash
|
|
10854
11069
|
};
|
|
10855
11070
|
}
|
|
10856
|
-
|
|
10857
|
-
* Create a resource only if it doesn't exist with the same definition hash
|
|
10858
|
-
* @param {Object} params - Resource parameters
|
|
10859
|
-
* @param {string} params.name - Resource name
|
|
10860
|
-
* @param {Object} params.attributes - Resource attributes
|
|
10861
|
-
* @param {Object} params.options - Resource options
|
|
10862
|
-
* @param {string} params.behavior - Resource behavior
|
|
10863
|
-
* @returns {Object} Object with resource and created flag
|
|
10864
|
-
*/
|
|
10865
|
-
async createResourceIfNotExists({ name, attributes, options = {}, behavior = "user-management" }) {
|
|
10866
|
-
const alreadyExists = !!this.resources[name];
|
|
10867
|
-
const hashCheck = this.resourceExistsWithSameHash({ name, attributes, options, behavior });
|
|
10868
|
-
if (hashCheck.exists && hashCheck.sameHash) {
|
|
10869
|
-
return {
|
|
10870
|
-
resource: this.resources[name],
|
|
10871
|
-
created: false,
|
|
10872
|
-
reason: "Resource already exists with same definition hash"
|
|
10873
|
-
};
|
|
10874
|
-
}
|
|
10875
|
-
const resource = await this.createResource({ name, attributes, options, behavior });
|
|
10876
|
-
return {
|
|
10877
|
-
resource,
|
|
10878
|
-
created: !alreadyExists,
|
|
10879
|
-
reason: alreadyExists ? "Resource updated with new definition" : "New resource created"
|
|
10880
|
-
};
|
|
10881
|
-
}
|
|
10882
|
-
async createResource({ name, attributes, options = {}, behavior = "user-management" }) {
|
|
11071
|
+
async createResource({ name, attributes, behavior = "user-management", hooks, ...config }) {
|
|
10883
11072
|
if (this.resources[name]) {
|
|
10884
11073
|
const existingResource = this.resources[name];
|
|
10885
11074
|
Object.assign(existingResource.config, {
|
|
10886
11075
|
cache: this.cache,
|
|
10887
|
-
...
|
|
11076
|
+
...config
|
|
10888
11077
|
});
|
|
10889
11078
|
if (behavior) {
|
|
10890
11079
|
existingResource.behavior = behavior;
|
|
10891
11080
|
}
|
|
10892
11081
|
existingResource.updateAttributes(attributes);
|
|
11082
|
+
if (hooks) {
|
|
11083
|
+
for (const [event, hooksArr] of Object.entries(hooks)) {
|
|
11084
|
+
if (Array.isArray(hooksArr) && existingResource.hooks[event]) {
|
|
11085
|
+
for (const fn of hooksArr) {
|
|
11086
|
+
if (typeof fn === "function") {
|
|
11087
|
+
existingResource.hooks[event].push(fn.bind(existingResource));
|
|
11088
|
+
}
|
|
11089
|
+
}
|
|
11090
|
+
}
|
|
11091
|
+
}
|
|
11092
|
+
}
|
|
10893
11093
|
const newHash = this.generateDefinitionHash(existingResource.export(), existingResource.behavior);
|
|
10894
11094
|
const existingMetadata2 = this.savedMetadata?.resources?.[name];
|
|
10895
11095
|
const currentVersion = existingMetadata2?.currentVersion || "v0";
|
|
@@ -10911,7 +11111,8 @@ class Database extends EventEmitter {
|
|
|
10911
11111
|
version,
|
|
10912
11112
|
passphrase: this.passphrase,
|
|
10913
11113
|
cache: this.cache,
|
|
10914
|
-
|
|
11114
|
+
hooks,
|
|
11115
|
+
...config
|
|
10915
11116
|
});
|
|
10916
11117
|
this.resources[name] = resource;
|
|
10917
11118
|
await this.uploadMetadataFile();
|
|
@@ -17613,4 +17814,4 @@ class CachePlugin extends Plugin {
|
|
|
17613
17814
|
}
|
|
17614
17815
|
}
|
|
17615
17816
|
|
|
17616
|
-
export { AuthenticationError, BaseError, Cache, CachePlugin, Client, ConnectionString, CostsPlugin, Database, DatabaseError, EncryptionError, ErrorMap, InvalidResourceItem, MemoryCache, MissingMetadata, NoSuchBucket, NoSuchKey, NotFound, PermissionError, Plugin, PluginObject, ResourceIdsPageReader, ResourceIdsReader, ResourceNotFound,
|
|
17817
|
+
export { AVAILABLE_BEHAVIORS, AuthenticationError, BaseError, Cache, CachePlugin, Client, ConnectionString, CostsPlugin, DEFAULT_BEHAVIOR, Database, DatabaseError, EncryptionError, ErrorMap, InvalidResourceItem, MemoryCache, MissingMetadata, NoSuchBucket, NoSuchKey, NotFound, PermissionError, Plugin, PluginObject, Resource, ResourceIdsPageReader, ResourceIdsReader, ResourceNotFound, ResourceReader, ResourceWriter, S3Cache, S3DBError, S3_DEFAULT_ENDPOINT, S3_DEFAULT_REGION, S3db, Schema, SchemaActions, UnknownError, ValidationError, Validator, ValidatorManager, behaviors, calculateAttributeNamesSize, calculateAttributeSizes, calculateTotalSize, calculateUTF8Bytes, decrypt, S3db as default, encrypt, getBehavior, getSizeBreakdown, idGenerator, passwordGenerator, sha256, streamToString, transformValue };
|