s3db.js 4.1.14 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/s3db.cjs.js +472 -247
- package/dist/s3db.cjs.min.js +11 -11
- package/dist/s3db.es.js +459 -246
- package/dist/s3db.es.min.js +13 -13
- package/dist/s3db.iife.js +472 -247
- 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
|
|
|
@@ -3388,7 +3388,7 @@ function requireJsonStableStringify () {
|
|
|
3388
3388
|
var jsonStableStringifyExports = requireJsonStableStringify();
|
|
3389
3389
|
var jsonStableStringify = /*@__PURE__*/getDefaultExportFromCjs(jsonStableStringifyExports);
|
|
3390
3390
|
|
|
3391
|
-
async function
|
|
3391
|
+
async function secretHandler(actual, errors, schema) {
|
|
3392
3392
|
if (!this.passphrase) {
|
|
3393
3393
|
errors.push({ actual, type: "encryptionKeyMissing" });
|
|
3394
3394
|
return actual;
|
|
@@ -3401,6 +3401,10 @@ async function custom(actual, errors, schema) {
|
|
|
3401
3401
|
}
|
|
3402
3402
|
return actual;
|
|
3403
3403
|
}
|
|
3404
|
+
async function jsonHandler(actual, errors, schema) {
|
|
3405
|
+
if (isString$1(actual)) return actual;
|
|
3406
|
+
return JSON.stringify(actual);
|
|
3407
|
+
}
|
|
3404
3408
|
class Validator extends FastestValidator {
|
|
3405
3409
|
constructor({ options, passphrase, autoEncrypt = true } = {}) {
|
|
3406
3410
|
super(merge({}, {
|
|
@@ -3422,7 +3426,7 @@ class Validator extends FastestValidator {
|
|
|
3422
3426
|
this.autoEncrypt = autoEncrypt;
|
|
3423
3427
|
this.alias("secret", {
|
|
3424
3428
|
type: "string",
|
|
3425
|
-
custom: this.autoEncrypt ?
|
|
3429
|
+
custom: this.autoEncrypt ? secretHandler : void 0,
|
|
3426
3430
|
messages: {
|
|
3427
3431
|
string: "The '{field}' field must be a string.",
|
|
3428
3432
|
stringMin: "This secret '{field}' field length must be at least {expected} long."
|
|
@@ -3430,11 +3434,15 @@ class Validator extends FastestValidator {
|
|
|
3430
3434
|
});
|
|
3431
3435
|
this.alias("secretAny", {
|
|
3432
3436
|
type: "any",
|
|
3433
|
-
custom: this.autoEncrypt ?
|
|
3437
|
+
custom: this.autoEncrypt ? secretHandler : void 0
|
|
3434
3438
|
});
|
|
3435
3439
|
this.alias("secretNumber", {
|
|
3436
3440
|
type: "number",
|
|
3437
|
-
custom: this.autoEncrypt ?
|
|
3441
|
+
custom: this.autoEncrypt ? secretHandler : void 0
|
|
3442
|
+
});
|
|
3443
|
+
this.alias("json", {
|
|
3444
|
+
type: "any",
|
|
3445
|
+
custom: this.autoEncrypt ? jsonHandler : void 0
|
|
3438
3446
|
});
|
|
3439
3447
|
}
|
|
3440
3448
|
}
|
|
@@ -3446,10 +3454,31 @@ const ValidatorManager = new Proxy(Validator, {
|
|
|
3446
3454
|
}
|
|
3447
3455
|
});
|
|
3448
3456
|
|
|
3457
|
+
function toBase36(num) {
|
|
3458
|
+
return num.toString(36);
|
|
3459
|
+
}
|
|
3460
|
+
function generateBase36Mapping(keys) {
|
|
3461
|
+
const mapping = {};
|
|
3462
|
+
const reversedMapping = {};
|
|
3463
|
+
keys.forEach((key, index) => {
|
|
3464
|
+
const base36Key = toBase36(index);
|
|
3465
|
+
mapping[key] = base36Key;
|
|
3466
|
+
reversedMapping[base36Key] = key;
|
|
3467
|
+
});
|
|
3468
|
+
return { mapping, reversedMapping };
|
|
3469
|
+
}
|
|
3449
3470
|
const SchemaActions = {
|
|
3450
3471
|
trim: (value) => value.trim(),
|
|
3451
3472
|
encrypt: (value, { passphrase }) => encrypt(value, passphrase),
|
|
3452
|
-
decrypt: (value, { passphrase }) =>
|
|
3473
|
+
decrypt: async (value, { passphrase }) => {
|
|
3474
|
+
try {
|
|
3475
|
+
const raw = await decrypt(value, passphrase);
|
|
3476
|
+
return raw;
|
|
3477
|
+
} catch (error) {
|
|
3478
|
+
console.warn(`Schema decrypt error: ${error}`, error);
|
|
3479
|
+
return value;
|
|
3480
|
+
}
|
|
3481
|
+
},
|
|
3453
3482
|
toString: (value) => String(value),
|
|
3454
3483
|
fromArray: (value, { separator }) => {
|
|
3455
3484
|
if (value === null || value === void 0 || !Array.isArray(value)) {
|
|
@@ -3540,8 +3569,9 @@ class Schema {
|
|
|
3540
3569
|
const leafKeys = Object.keys(flatAttrs).filter((k) => !k.includes("$$"));
|
|
3541
3570
|
const objectKeys = this.extractObjectKeys(this.attributes);
|
|
3542
3571
|
const allKeys = [.../* @__PURE__ */ new Set([...leafKeys, ...objectKeys])];
|
|
3543
|
-
|
|
3544
|
-
this.map =
|
|
3572
|
+
const { mapping, reversedMapping } = generateBase36Mapping(allKeys);
|
|
3573
|
+
this.map = mapping;
|
|
3574
|
+
this.reversedMap = reversedMapping;
|
|
3545
3575
|
}
|
|
3546
3576
|
}
|
|
3547
3577
|
defaultOptions() {
|
|
@@ -3582,24 +3612,27 @@ class Schema {
|
|
|
3582
3612
|
if (definition.includes("array")) {
|
|
3583
3613
|
this.addHook("beforeMap", name, "fromArray");
|
|
3584
3614
|
this.addHook("afterUnmap", name, "toArray");
|
|
3585
|
-
}
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
}
|
|
3590
|
-
if (this.options.autoDecrypt) {
|
|
3591
|
-
this.addHook("afterUnmap", name, "decrypt");
|
|
3592
|
-
}
|
|
3593
|
-
}
|
|
3594
|
-
if (definition.includes("number")) {
|
|
3595
|
-
this.addHook("beforeMap", name, "toString");
|
|
3596
|
-
this.addHook("afterUnmap", name, "toNumber");
|
|
3615
|
+
}
|
|
3616
|
+
if (definition.includes("secret")) {
|
|
3617
|
+
if (this.options.autoEncrypt) {
|
|
3618
|
+
this.addHook("beforeMap", name, "encrypt");
|
|
3597
3619
|
}
|
|
3598
|
-
if (
|
|
3599
|
-
this.addHook("
|
|
3600
|
-
this.addHook("afterUnmap", name, "toBool");
|
|
3620
|
+
if (this.options.autoDecrypt) {
|
|
3621
|
+
this.addHook("afterUnmap", name, "decrypt");
|
|
3601
3622
|
}
|
|
3602
3623
|
}
|
|
3624
|
+
if (definition.includes("number")) {
|
|
3625
|
+
this.addHook("beforeMap", name, "toString");
|
|
3626
|
+
this.addHook("afterUnmap", name, "toNumber");
|
|
3627
|
+
}
|
|
3628
|
+
if (definition.includes("boolean")) {
|
|
3629
|
+
this.addHook("beforeMap", name, "fromBool");
|
|
3630
|
+
this.addHook("afterUnmap", name, "toBool");
|
|
3631
|
+
}
|
|
3632
|
+
if (definition.includes("json")) {
|
|
3633
|
+
this.addHook("beforeMap", name, "toJSON");
|
|
3634
|
+
this.addHook("afterUnmap", name, "fromJSON");
|
|
3635
|
+
}
|
|
3603
3636
|
}
|
|
3604
3637
|
}
|
|
3605
3638
|
static import(data) {
|
|
@@ -8614,7 +8647,6 @@ function calculateUTF8Bytes(str) {
|
|
|
8614
8647
|
function calculateAttributeNamesSize(mappedObject) {
|
|
8615
8648
|
let totalSize = 0;
|
|
8616
8649
|
for (const key of Object.keys(mappedObject)) {
|
|
8617
|
-
if (key === "_v") continue;
|
|
8618
8650
|
totalSize += calculateUTF8Bytes(key);
|
|
8619
8651
|
}
|
|
8620
8652
|
return totalSize;
|
|
@@ -8658,6 +8690,30 @@ function calculateTotalSize(mappedObject) {
|
|
|
8658
8690
|
const namesSize = calculateAttributeNamesSize(mappedObject);
|
|
8659
8691
|
return valueTotal + namesSize;
|
|
8660
8692
|
}
|
|
8693
|
+
function getSizeBreakdown(mappedObject) {
|
|
8694
|
+
const valueSizes = calculateAttributeSizes(mappedObject);
|
|
8695
|
+
const namesSize = calculateAttributeNamesSize(mappedObject);
|
|
8696
|
+
const valueTotal = Object.values(valueSizes).reduce((sum, size) => sum + size, 0);
|
|
8697
|
+
const total = valueTotal + namesSize;
|
|
8698
|
+
const sortedAttributes = Object.entries(valueSizes).sort(([, a], [, b]) => b - a).map(([key, size]) => ({
|
|
8699
|
+
attribute: key,
|
|
8700
|
+
size,
|
|
8701
|
+
percentage: (size / total * 100).toFixed(2) + "%"
|
|
8702
|
+
}));
|
|
8703
|
+
return {
|
|
8704
|
+
total,
|
|
8705
|
+
valueSizes,
|
|
8706
|
+
namesSize,
|
|
8707
|
+
valueTotal,
|
|
8708
|
+
breakdown: sortedAttributes,
|
|
8709
|
+
// Add detailed breakdown including names
|
|
8710
|
+
detailedBreakdown: {
|
|
8711
|
+
values: valueTotal,
|
|
8712
|
+
names: namesSize,
|
|
8713
|
+
total
|
|
8714
|
+
}
|
|
8715
|
+
};
|
|
8716
|
+
}
|
|
8661
8717
|
|
|
8662
8718
|
const S3_METADATA_LIMIT_BYTES = 2048;
|
|
8663
8719
|
async function handleInsert$3({ resource, data, mappedData }) {
|
|
@@ -8878,6 +8934,7 @@ function getBehavior(behaviorName) {
|
|
|
8878
8934
|
}
|
|
8879
8935
|
return behavior;
|
|
8880
8936
|
}
|
|
8937
|
+
const AVAILABLE_BEHAVIORS = Object.keys(behaviors);
|
|
8881
8938
|
const DEFAULT_BEHAVIOR = "user-management";
|
|
8882
8939
|
|
|
8883
8940
|
class Resource extends EventEmitter {
|
|
@@ -8947,17 +9004,8 @@ ${validation.errors.join("\n")}`);
|
|
|
8947
9004
|
partitions = {},
|
|
8948
9005
|
paranoid = true,
|
|
8949
9006
|
allNestedObjectsOptional = true,
|
|
8950
|
-
hooks = {}
|
|
8951
|
-
options = {}
|
|
9007
|
+
hooks = {}
|
|
8952
9008
|
} = 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
9009
|
this.name = name;
|
|
8962
9010
|
this.client = client;
|
|
8963
9011
|
this.version = version;
|
|
@@ -8966,13 +9014,13 @@ ${validation.errors.join("\n")}`);
|
|
|
8966
9014
|
this.parallelism = parallelism;
|
|
8967
9015
|
this.passphrase = passphrase ?? "secret";
|
|
8968
9016
|
this.config = {
|
|
8969
|
-
cache
|
|
9017
|
+
cache,
|
|
8970
9018
|
hooks,
|
|
8971
|
-
paranoid
|
|
8972
|
-
timestamps
|
|
8973
|
-
partitions
|
|
8974
|
-
autoDecrypt
|
|
8975
|
-
allNestedObjectsOptional
|
|
9019
|
+
paranoid,
|
|
9020
|
+
timestamps,
|
|
9021
|
+
partitions,
|
|
9022
|
+
autoDecrypt,
|
|
9023
|
+
allNestedObjectsOptional
|
|
8976
9024
|
};
|
|
8977
9025
|
this.hooks = {
|
|
8978
9026
|
preInsert: [],
|
|
@@ -8983,39 +9031,7 @@ ${validation.errors.join("\n")}`);
|
|
|
8983
9031
|
afterDelete: []
|
|
8984
9032
|
};
|
|
8985
9033
|
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();
|
|
9034
|
+
this.applyConfiguration();
|
|
9019
9035
|
if (hooks) {
|
|
9020
9036
|
for (const [event, hooksArr] of Object.entries(hooks)) {
|
|
9021
9037
|
if (Array.isArray(hooksArr) && this.hooks[event]) {
|
|
@@ -9054,15 +9070,17 @@ ${validation.errors.join("\n")}`);
|
|
|
9054
9070
|
return exported;
|
|
9055
9071
|
}
|
|
9056
9072
|
/**
|
|
9057
|
-
*
|
|
9058
|
-
*
|
|
9073
|
+
* Apply configuration settings (timestamps, partitions, hooks)
|
|
9074
|
+
* This method ensures that all configuration-dependent features are properly set up
|
|
9059
9075
|
*/
|
|
9060
|
-
|
|
9061
|
-
const oldAttributes = this.attributes;
|
|
9062
|
-
this.attributes = newAttributes;
|
|
9076
|
+
applyConfiguration() {
|
|
9063
9077
|
if (this.config.timestamps) {
|
|
9064
|
-
|
|
9065
|
-
|
|
9078
|
+
if (!this.attributes.createdAt) {
|
|
9079
|
+
this.attributes.createdAt = "string|optional";
|
|
9080
|
+
}
|
|
9081
|
+
if (!this.attributes.updatedAt) {
|
|
9082
|
+
this.attributes.updatedAt = "string|optional";
|
|
9083
|
+
}
|
|
9066
9084
|
if (!this.config.partitions) {
|
|
9067
9085
|
this.config.partitions = {};
|
|
9068
9086
|
}
|
|
@@ -9081,9 +9099,10 @@ ${validation.errors.join("\n")}`);
|
|
|
9081
9099
|
};
|
|
9082
9100
|
}
|
|
9083
9101
|
}
|
|
9102
|
+
this.setupPartitionHooks();
|
|
9084
9103
|
this.schema = new Schema({
|
|
9085
9104
|
name: this.name,
|
|
9086
|
-
attributes:
|
|
9105
|
+
attributes: this.attributes,
|
|
9087
9106
|
passphrase: this.passphrase,
|
|
9088
9107
|
version: this.version,
|
|
9089
9108
|
options: {
|
|
@@ -9091,8 +9110,16 @@ ${validation.errors.join("\n")}`);
|
|
|
9091
9110
|
allNestedObjectsOptional: this.config.allNestedObjectsOptional
|
|
9092
9111
|
}
|
|
9093
9112
|
});
|
|
9094
|
-
this.setupPartitionHooks();
|
|
9095
9113
|
this.validatePartitions();
|
|
9114
|
+
}
|
|
9115
|
+
/**
|
|
9116
|
+
* Update resource attributes and rebuild schema
|
|
9117
|
+
* @param {Object} newAttributes - New attributes definition
|
|
9118
|
+
*/
|
|
9119
|
+
updateAttributes(newAttributes) {
|
|
9120
|
+
const oldAttributes = this.attributes;
|
|
9121
|
+
this.attributes = newAttributes;
|
|
9122
|
+
this.applyConfiguration();
|
|
9096
9123
|
return { oldAttributes, newAttributes };
|
|
9097
9124
|
}
|
|
9098
9125
|
/**
|
|
@@ -9180,7 +9207,7 @@ ${validation.errors.join("\n")}`);
|
|
|
9180
9207
|
for (const fieldName of Object.keys(partitionDef.fields)) {
|
|
9181
9208
|
if (!this.fieldExistsInAttributes(fieldName)) {
|
|
9182
9209
|
throw new Error(
|
|
9183
|
-
`Partition '${partitionName}' uses field '${fieldName}' which does not exist in resource
|
|
9210
|
+
`Partition '${partitionName}' uses field '${fieldName}' which does not exist in resource attributes. Available fields: ${currentAttributes.join(", ")}.`
|
|
9184
9211
|
);
|
|
9185
9212
|
}
|
|
9186
9213
|
}
|
|
@@ -9293,7 +9320,11 @@ ${validation.errors.join("\n")}`);
|
|
|
9293
9320
|
if (partitionSegments.length === 0) {
|
|
9294
9321
|
return null;
|
|
9295
9322
|
}
|
|
9296
|
-
|
|
9323
|
+
const finalId = id || data?.id;
|
|
9324
|
+
if (!finalId) {
|
|
9325
|
+
return null;
|
|
9326
|
+
}
|
|
9327
|
+
return join(`resource=${this.name}`, `partition=${partitionName}`, ...partitionSegments, `id=${finalId}`);
|
|
9297
9328
|
}
|
|
9298
9329
|
/**
|
|
9299
9330
|
* Get nested field value from data object using dot notation
|
|
@@ -9522,12 +9553,12 @@ ${validation.errors.join("\n")}`);
|
|
|
9522
9553
|
* console.log(updatedUser.updatedAt); // ISO timestamp
|
|
9523
9554
|
*/
|
|
9524
9555
|
async update(id, attributes) {
|
|
9525
|
-
const
|
|
9556
|
+
const originalData = await this.get(id);
|
|
9526
9557
|
if (this.config.timestamps) {
|
|
9527
9558
|
attributes.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9528
9559
|
}
|
|
9529
9560
|
const preProcessedData = await this.executeHooks("preUpdate", attributes);
|
|
9530
|
-
const attrs = merge(
|
|
9561
|
+
const attrs = merge(originalData, preProcessedData);
|
|
9531
9562
|
delete attrs.id;
|
|
9532
9563
|
const { isValid, errors, data: validated } = await this.validate(attrs);
|
|
9533
9564
|
if (!isValid) {
|
|
@@ -9538,6 +9569,9 @@ ${validation.errors.join("\n")}`);
|
|
|
9538
9569
|
validation: errors
|
|
9539
9570
|
});
|
|
9540
9571
|
}
|
|
9572
|
+
const oldData = { ...originalData, id };
|
|
9573
|
+
const newData = { ...validated, id };
|
|
9574
|
+
await this.handlePartitionReferenceUpdates(oldData, newData);
|
|
9541
9575
|
const mappedData = await this.schema.mapper(validated);
|
|
9542
9576
|
const behaviorImpl = getBehavior(this.behavior);
|
|
9543
9577
|
const { mappedData: processedMetadata, body } = await behaviorImpl.handleUpdate({
|
|
@@ -9573,7 +9607,6 @@ ${validation.errors.join("\n")}`);
|
|
|
9573
9607
|
});
|
|
9574
9608
|
validated.id = id;
|
|
9575
9609
|
await this.executeHooks("afterUpdate", validated);
|
|
9576
|
-
await this.updatePartitionReferences(validated);
|
|
9577
9610
|
this.emit("update", preProcessedData, validated);
|
|
9578
9611
|
return validated;
|
|
9579
9612
|
}
|
|
@@ -9860,23 +9893,104 @@ ${validation.errors.join("\n")}`);
|
|
|
9860
9893
|
* });
|
|
9861
9894
|
*/
|
|
9862
9895
|
async list({ partition = null, partitionValues = {}, limit, offset = 0 } = {}) {
|
|
9863
|
-
|
|
9864
|
-
|
|
9865
|
-
|
|
9896
|
+
try {
|
|
9897
|
+
if (!partition) {
|
|
9898
|
+
let ids2 = [];
|
|
9899
|
+
try {
|
|
9900
|
+
ids2 = await this.listIds({ partition, partitionValues });
|
|
9901
|
+
} catch (listIdsError) {
|
|
9902
|
+
console.warn(`Failed to get list IDs:`, listIdsError.message);
|
|
9903
|
+
return [];
|
|
9904
|
+
}
|
|
9905
|
+
let filteredIds2 = ids2.slice(offset);
|
|
9906
|
+
if (limit) {
|
|
9907
|
+
filteredIds2 = filteredIds2.slice(0, limit);
|
|
9908
|
+
}
|
|
9909
|
+
const { results: results2, errors: errors2 } = await PromisePool.for(filteredIds2).withConcurrency(this.parallelism).handleError(async (error, id) => {
|
|
9910
|
+
console.warn(`Failed to get resource ${id}:`, error.message);
|
|
9911
|
+
return null;
|
|
9912
|
+
}).process(async (id) => {
|
|
9913
|
+
try {
|
|
9914
|
+
return await this.get(id);
|
|
9915
|
+
} catch (error) {
|
|
9916
|
+
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9917
|
+
console.warn(`Decryption failed for ${id}, returning basic info`);
|
|
9918
|
+
return {
|
|
9919
|
+
id,
|
|
9920
|
+
_decryptionFailed: true,
|
|
9921
|
+
_error: error.message
|
|
9922
|
+
};
|
|
9923
|
+
}
|
|
9924
|
+
throw error;
|
|
9925
|
+
}
|
|
9926
|
+
});
|
|
9927
|
+
const validResults2 = results2.filter((item) => item !== null);
|
|
9928
|
+
this.emit("list", { partition, partitionValues, count: validResults2.length, errors: errors2.length });
|
|
9929
|
+
return validResults2;
|
|
9930
|
+
}
|
|
9931
|
+
if (!this.config.partitions || !this.config.partitions[partition]) {
|
|
9932
|
+
console.warn(`Partition '${partition}' not found in resource '${this.name}'`);
|
|
9933
|
+
this.emit("list", { partition, partitionValues, count: 0, errors: 0 });
|
|
9934
|
+
return [];
|
|
9935
|
+
}
|
|
9936
|
+
const partitionDef = this.config.partitions[partition];
|
|
9937
|
+
const partitionSegments = [];
|
|
9938
|
+
const sortedFields = Object.entries(partitionDef.fields).sort(([a], [b]) => a.localeCompare(b));
|
|
9939
|
+
for (const [fieldName, rule] of sortedFields) {
|
|
9940
|
+
const value = partitionValues[fieldName];
|
|
9941
|
+
if (value !== void 0 && value !== null) {
|
|
9942
|
+
const transformedValue = this.applyPartitionRule(value, rule);
|
|
9943
|
+
partitionSegments.push(`${fieldName}=${transformedValue}`);
|
|
9944
|
+
}
|
|
9945
|
+
}
|
|
9946
|
+
let prefix;
|
|
9947
|
+
if (partitionSegments.length > 0) {
|
|
9948
|
+
prefix = `resource=${this.name}/partition=${partition}/${partitionSegments.join("/")}`;
|
|
9949
|
+
} else {
|
|
9950
|
+
prefix = `resource=${this.name}/partition=${partition}`;
|
|
9951
|
+
}
|
|
9952
|
+
let keys = [];
|
|
9953
|
+
try {
|
|
9954
|
+
keys = await this.client.getAllKeys({ prefix });
|
|
9955
|
+
} catch (getKeysError) {
|
|
9956
|
+
console.warn(`Failed to get partition keys:`, getKeysError.message);
|
|
9957
|
+
return [];
|
|
9958
|
+
}
|
|
9959
|
+
const ids = keys.map((key) => {
|
|
9960
|
+
const parts = key.split("/");
|
|
9961
|
+
const idPart = parts.find((part) => part.startsWith("id="));
|
|
9962
|
+
return idPart ? idPart.replace("id=", "") : null;
|
|
9963
|
+
}).filter(Boolean);
|
|
9964
|
+
let filteredIds = ids.slice(offset);
|
|
9866
9965
|
if (limit) {
|
|
9867
|
-
|
|
9966
|
+
filteredIds = filteredIds.slice(0, limit);
|
|
9868
9967
|
}
|
|
9869
|
-
const { results
|
|
9870
|
-
console.warn(`Failed to get resource ${id}:`, error.message);
|
|
9968
|
+
const { results, errors } = await PromisePool.for(filteredIds).withConcurrency(this.parallelism).handleError(async (error, id) => {
|
|
9969
|
+
console.warn(`Failed to get partition resource ${id}:`, error.message);
|
|
9871
9970
|
return null;
|
|
9872
9971
|
}).process(async (id) => {
|
|
9873
9972
|
try {
|
|
9874
|
-
|
|
9973
|
+
const keyForId = keys.find((key) => key.includes(`id=${id}`));
|
|
9974
|
+
if (!keyForId) {
|
|
9975
|
+
throw new Error(`Partition key not found for ID ${id}`);
|
|
9976
|
+
}
|
|
9977
|
+
const keyParts = keyForId.split("/");
|
|
9978
|
+
const actualPartitionValues = {};
|
|
9979
|
+
for (const [fieldName, rule] of sortedFields) {
|
|
9980
|
+
const fieldPart = keyParts.find((part) => part.startsWith(`${fieldName}=`));
|
|
9981
|
+
if (fieldPart) {
|
|
9982
|
+
const value = fieldPart.replace(`${fieldName}=`, "");
|
|
9983
|
+
actualPartitionValues[fieldName] = value;
|
|
9984
|
+
}
|
|
9985
|
+
}
|
|
9986
|
+
return await this.getFromPartition({ id, partitionName: partition, partitionValues: actualPartitionValues });
|
|
9875
9987
|
} catch (error) {
|
|
9876
9988
|
if (error.message.includes("Cipher job failed") || error.message.includes("OperationError")) {
|
|
9877
|
-
console.warn(`Decryption failed for ${id}, returning basic info`);
|
|
9989
|
+
console.warn(`Decryption failed for partition resource ${id}, returning basic info`);
|
|
9878
9990
|
return {
|
|
9879
9991
|
id,
|
|
9992
|
+
_partition: partition,
|
|
9993
|
+
_partitionValues: partitionValues,
|
|
9880
9994
|
_decryptionFailed: true,
|
|
9881
9995
|
_error: error.message
|
|
9882
9996
|
};
|
|
@@ -9884,62 +9998,19 @@ ${validation.errors.join("\n")}`);
|
|
|
9884
9998
|
throw error;
|
|
9885
9999
|
}
|
|
9886
10000
|
});
|
|
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}`);
|
|
10001
|
+
const validResults = results.filter((item) => item !== null);
|
|
10002
|
+
this.emit("list", { partition, partitionValues, count: validResults.length, errors: errors.length });
|
|
10003
|
+
return validResults;
|
|
10004
|
+
} catch (error) {
|
|
10005
|
+
if (error.message.includes("Partition '") && error.message.includes("' not found")) {
|
|
10006
|
+
console.warn(`Partition error in list method:`, error.message);
|
|
10007
|
+
this.emit("list", { partition, partitionValues, count: 0, errors: 1 });
|
|
10008
|
+
return [];
|
|
9902
10009
|
}
|
|
10010
|
+
console.error(`Critical error in list method:`, error.message);
|
|
10011
|
+
this.emit("list", { partition, partitionValues, count: 0, errors: 1 });
|
|
10012
|
+
return [];
|
|
9903
10013
|
}
|
|
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
10014
|
}
|
|
9944
10015
|
/**
|
|
9945
10016
|
* Get multiple resources by their IDs
|
|
@@ -10046,36 +10117,67 @@ ${validation.errors.join("\n")}`);
|
|
|
10046
10117
|
* console.log(`Got ${fastPage.items.length} items`); // totalItems will be null
|
|
10047
10118
|
*/
|
|
10048
10119
|
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
|
|
10120
|
+
try {
|
|
10121
|
+
let totalItems = null;
|
|
10122
|
+
let totalPages = null;
|
|
10123
|
+
if (!skipCount) {
|
|
10124
|
+
try {
|
|
10125
|
+
totalItems = await this.count({ partition, partitionValues });
|
|
10126
|
+
totalPages = Math.ceil(totalItems / size);
|
|
10127
|
+
} catch (countError) {
|
|
10128
|
+
console.warn(`Failed to get count for page:`, countError.message);
|
|
10129
|
+
totalItems = null;
|
|
10130
|
+
totalPages = null;
|
|
10131
|
+
}
|
|
10075
10132
|
}
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10133
|
+
const page = Math.floor(offset / size);
|
|
10134
|
+
let items = [];
|
|
10135
|
+
try {
|
|
10136
|
+
items = await this.list({
|
|
10137
|
+
partition,
|
|
10138
|
+
partitionValues,
|
|
10139
|
+
limit: size,
|
|
10140
|
+
offset
|
|
10141
|
+
});
|
|
10142
|
+
} catch (listError) {
|
|
10143
|
+
console.warn(`Failed to get items for page:`, listError.message);
|
|
10144
|
+
items = [];
|
|
10145
|
+
}
|
|
10146
|
+
const result = {
|
|
10147
|
+
items,
|
|
10148
|
+
totalItems,
|
|
10149
|
+
page,
|
|
10150
|
+
pageSize: size,
|
|
10151
|
+
totalPages,
|
|
10152
|
+
// Add additional metadata for debugging
|
|
10153
|
+
_debug: {
|
|
10154
|
+
requestedSize: size,
|
|
10155
|
+
requestedOffset: offset,
|
|
10156
|
+
actualItemsReturned: items.length,
|
|
10157
|
+
skipCount,
|
|
10158
|
+
hasTotalItems: totalItems !== null
|
|
10159
|
+
}
|
|
10160
|
+
};
|
|
10161
|
+
this.emit("page", result);
|
|
10162
|
+
return result;
|
|
10163
|
+
} catch (error) {
|
|
10164
|
+
console.error(`Critical error in page method:`, error.message);
|
|
10165
|
+
return {
|
|
10166
|
+
items: [],
|
|
10167
|
+
totalItems: null,
|
|
10168
|
+
page: Math.floor(offset / size),
|
|
10169
|
+
pageSize: size,
|
|
10170
|
+
totalPages: null,
|
|
10171
|
+
_debug: {
|
|
10172
|
+
requestedSize: size,
|
|
10173
|
+
requestedOffset: offset,
|
|
10174
|
+
actualItemsReturned: 0,
|
|
10175
|
+
skipCount,
|
|
10176
|
+
hasTotalItems: false,
|
|
10177
|
+
error: error.message
|
|
10178
|
+
}
|
|
10179
|
+
};
|
|
10180
|
+
}
|
|
10079
10181
|
}
|
|
10080
10182
|
readable() {
|
|
10081
10183
|
const stream = new ResourceReader({ resource: this });
|
|
@@ -10368,7 +10470,118 @@ ${validation.errors.join("\n")}`);
|
|
|
10368
10470
|
return results.slice(0, limit);
|
|
10369
10471
|
}
|
|
10370
10472
|
/**
|
|
10371
|
-
*
|
|
10473
|
+
* Handle partition reference updates with change detection
|
|
10474
|
+
* @param {Object} oldData - Original object data before update
|
|
10475
|
+
* @param {Object} newData - Updated object data
|
|
10476
|
+
*/
|
|
10477
|
+
async handlePartitionReferenceUpdates(oldData, newData) {
|
|
10478
|
+
const partitions = this.config.partitions;
|
|
10479
|
+
if (!partitions || Object.keys(partitions).length === 0) {
|
|
10480
|
+
return;
|
|
10481
|
+
}
|
|
10482
|
+
for (const [partitionName, partition] of Object.entries(partitions)) {
|
|
10483
|
+
try {
|
|
10484
|
+
await this.handlePartitionReferenceUpdate(partitionName, partition, oldData, newData);
|
|
10485
|
+
} catch (error) {
|
|
10486
|
+
console.warn(`Failed to update partition references for ${partitionName}:`, error.message);
|
|
10487
|
+
}
|
|
10488
|
+
}
|
|
10489
|
+
const id = newData.id || oldData.id;
|
|
10490
|
+
for (const [partitionName, partition] of Object.entries(partitions)) {
|
|
10491
|
+
const prefix = `resource=${this.name}/partition=${partitionName}`;
|
|
10492
|
+
let allKeys = [];
|
|
10493
|
+
try {
|
|
10494
|
+
allKeys = await this.client.getAllKeys({ prefix });
|
|
10495
|
+
} catch (error) {
|
|
10496
|
+
console.warn(`Aggressive cleanup: could not list keys for partition ${partitionName}:`, error.message);
|
|
10497
|
+
continue;
|
|
10498
|
+
}
|
|
10499
|
+
const validKey = this.getPartitionKey({ partitionName, id, data: newData });
|
|
10500
|
+
for (const key of allKeys) {
|
|
10501
|
+
if (key.endsWith(`/id=${id}`) && key !== validKey) {
|
|
10502
|
+
try {
|
|
10503
|
+
await this.client.deleteObject(key);
|
|
10504
|
+
} catch (error) {
|
|
10505
|
+
console.warn(`Aggressive cleanup: could not delete stale partition key ${key}:`, error.message);
|
|
10506
|
+
}
|
|
10507
|
+
}
|
|
10508
|
+
}
|
|
10509
|
+
}
|
|
10510
|
+
}
|
|
10511
|
+
/**
|
|
10512
|
+
* Handle partition reference update for a specific partition
|
|
10513
|
+
* @param {string} partitionName - Name of the partition
|
|
10514
|
+
* @param {Object} partition - Partition definition
|
|
10515
|
+
* @param {Object} oldData - Original object data before update
|
|
10516
|
+
* @param {Object} newData - Updated object data
|
|
10517
|
+
*/
|
|
10518
|
+
async handlePartitionReferenceUpdate(partitionName, partition, oldData, newData) {
|
|
10519
|
+
const id = newData.id || oldData.id;
|
|
10520
|
+
const oldPartitionKey = this.getPartitionKey({ partitionName, id, data: oldData });
|
|
10521
|
+
const newPartitionKey = this.getPartitionKey({ partitionName, id, data: newData });
|
|
10522
|
+
if (oldPartitionKey !== newPartitionKey) {
|
|
10523
|
+
if (oldPartitionKey) {
|
|
10524
|
+
try {
|
|
10525
|
+
await this.client.deleteObject(oldPartitionKey);
|
|
10526
|
+
} catch (error) {
|
|
10527
|
+
console.warn(`Old partition object could not be deleted for ${partitionName}:`, error.message);
|
|
10528
|
+
}
|
|
10529
|
+
}
|
|
10530
|
+
if (newPartitionKey) {
|
|
10531
|
+
try {
|
|
10532
|
+
const mappedData = await this.schema.mapper(newData);
|
|
10533
|
+
if (mappedData.undefined !== void 0) delete mappedData.undefined;
|
|
10534
|
+
const behaviorImpl = getBehavior(this.behavior);
|
|
10535
|
+
const { mappedData: processedMetadata, body } = await behaviorImpl.handleUpdate({
|
|
10536
|
+
resource: this,
|
|
10537
|
+
id,
|
|
10538
|
+
data: newData,
|
|
10539
|
+
mappedData
|
|
10540
|
+
});
|
|
10541
|
+
if (processedMetadata.undefined !== void 0) delete processedMetadata.undefined;
|
|
10542
|
+
const partitionMetadata = {
|
|
10543
|
+
...processedMetadata,
|
|
10544
|
+
_version: this.version
|
|
10545
|
+
};
|
|
10546
|
+
if (partitionMetadata.undefined !== void 0) delete partitionMetadata.undefined;
|
|
10547
|
+
await this.client.putObject({
|
|
10548
|
+
key: newPartitionKey,
|
|
10549
|
+
metadata: partitionMetadata,
|
|
10550
|
+
body
|
|
10551
|
+
});
|
|
10552
|
+
} catch (error) {
|
|
10553
|
+
console.warn(`New partition object could not be created for ${partitionName}:`, error.message);
|
|
10554
|
+
}
|
|
10555
|
+
}
|
|
10556
|
+
} else if (newPartitionKey) {
|
|
10557
|
+
try {
|
|
10558
|
+
const mappedData = await this.schema.mapper(newData);
|
|
10559
|
+
if (mappedData.undefined !== void 0) delete mappedData.undefined;
|
|
10560
|
+
const behaviorImpl = getBehavior(this.behavior);
|
|
10561
|
+
const { mappedData: processedMetadata, body } = await behaviorImpl.handleUpdate({
|
|
10562
|
+
resource: this,
|
|
10563
|
+
id,
|
|
10564
|
+
data: newData,
|
|
10565
|
+
mappedData
|
|
10566
|
+
});
|
|
10567
|
+
if (processedMetadata.undefined !== void 0) delete processedMetadata.undefined;
|
|
10568
|
+
const partitionMetadata = {
|
|
10569
|
+
...processedMetadata,
|
|
10570
|
+
_version: this.version
|
|
10571
|
+
};
|
|
10572
|
+
if (partitionMetadata.undefined !== void 0) delete partitionMetadata.undefined;
|
|
10573
|
+
await this.client.putObject({
|
|
10574
|
+
key: newPartitionKey,
|
|
10575
|
+
metadata: partitionMetadata,
|
|
10576
|
+
body
|
|
10577
|
+
});
|
|
10578
|
+
} catch (error) {
|
|
10579
|
+
console.warn(`Partition object could not be updated for ${partitionName}:`, error.message);
|
|
10580
|
+
}
|
|
10581
|
+
}
|
|
10582
|
+
}
|
|
10583
|
+
/**
|
|
10584
|
+
* Update partition objects to keep them in sync (legacy method for backward compatibility)
|
|
10372
10585
|
* @param {Object} data - Updated object data
|
|
10373
10586
|
*/
|
|
10374
10587
|
async updatePartitionReferences(data) {
|
|
@@ -10377,6 +10590,10 @@ ${validation.errors.join("\n")}`);
|
|
|
10377
10590
|
return;
|
|
10378
10591
|
}
|
|
10379
10592
|
for (const [partitionName, partition] of Object.entries(partitions)) {
|
|
10593
|
+
if (!partition || !partition.fields || typeof partition.fields !== "object") {
|
|
10594
|
+
console.warn(`Skipping invalid partition '${partitionName}' in resource '${this.name}'`);
|
|
10595
|
+
continue;
|
|
10596
|
+
}
|
|
10380
10597
|
const partitionKey = this.getPartitionKey({ partitionName, id: data.id, data });
|
|
10381
10598
|
if (partitionKey) {
|
|
10382
10599
|
const mappedData = await this.schema.mapper(data);
|
|
@@ -10475,6 +10692,7 @@ ${validation.errors.join("\n")}`);
|
|
|
10475
10692
|
if (request.VersionId) data._versionId = request.VersionId;
|
|
10476
10693
|
if (request.Expiration) data._expiresAt = request.Expiration;
|
|
10477
10694
|
data._definitionHash = this.getDefinitionHash();
|
|
10695
|
+
if (data.undefined !== void 0) delete data.undefined;
|
|
10478
10696
|
this.emit("getFromPartition", data);
|
|
10479
10697
|
return data;
|
|
10480
10698
|
}
|
|
@@ -10578,7 +10796,7 @@ class Database extends EventEmitter {
|
|
|
10578
10796
|
this.version = "1";
|
|
10579
10797
|
this.s3dbVersion = (() => {
|
|
10580
10798
|
try {
|
|
10581
|
-
return true ? "
|
|
10799
|
+
return true ? "5.0.0" : "latest";
|
|
10582
10800
|
} catch (e) {
|
|
10583
10801
|
return "latest";
|
|
10584
10802
|
}
|
|
@@ -10593,14 +10811,24 @@ class Database extends EventEmitter {
|
|
|
10593
10811
|
this.passphrase = options.passphrase || "secret";
|
|
10594
10812
|
let connectionString = options.connectionString;
|
|
10595
10813
|
if (!connectionString && (options.bucket || options.accessKeyId || options.secretAccessKey)) {
|
|
10596
|
-
|
|
10597
|
-
|
|
10598
|
-
|
|
10599
|
-
accessKeyId
|
|
10600
|
-
secretAccessKey
|
|
10601
|
-
|
|
10602
|
-
forcePathStyle
|
|
10603
|
-
|
|
10814
|
+
const { bucket, region, accessKeyId, secretAccessKey, endpoint, forcePathStyle } = options;
|
|
10815
|
+
if (endpoint) {
|
|
10816
|
+
const url = new URL(endpoint);
|
|
10817
|
+
if (accessKeyId) url.username = encodeURIComponent(accessKeyId);
|
|
10818
|
+
if (secretAccessKey) url.password = encodeURIComponent(secretAccessKey);
|
|
10819
|
+
url.pathname = `/${bucket || "s3db"}`;
|
|
10820
|
+
if (forcePathStyle) {
|
|
10821
|
+
url.searchParams.set("forcePathStyle", "true");
|
|
10822
|
+
}
|
|
10823
|
+
connectionString = url.toString();
|
|
10824
|
+
} else if (accessKeyId && secretAccessKey) {
|
|
10825
|
+
const params = new URLSearchParams();
|
|
10826
|
+
params.set("region", region || "us-east-1");
|
|
10827
|
+
if (forcePathStyle) {
|
|
10828
|
+
params.set("forcePathStyle", "true");
|
|
10829
|
+
}
|
|
10830
|
+
connectionString = `s3://${encodeURIComponent(accessKeyId)}:${encodeURIComponent(secretAccessKey)}@${bucket || "s3db"}?${params.toString()}`;
|
|
10831
|
+
}
|
|
10604
10832
|
}
|
|
10605
10833
|
this.client = options.client || new Client({
|
|
10606
10834
|
verbose: this.verbose,
|
|
@@ -10636,12 +10864,12 @@ class Database extends EventEmitter {
|
|
|
10636
10864
|
passphrase: this.passphrase,
|
|
10637
10865
|
observers: [this],
|
|
10638
10866
|
cache: this.cache,
|
|
10639
|
-
timestamps: versionData.
|
|
10640
|
-
partitions: resourceMetadata.partitions || versionData.
|
|
10641
|
-
paranoid: versionData.
|
|
10642
|
-
allNestedObjectsOptional: versionData.
|
|
10643
|
-
autoDecrypt: versionData.
|
|
10644
|
-
hooks: {}
|
|
10867
|
+
timestamps: versionData.timestamps !== void 0 ? versionData.timestamps : false,
|
|
10868
|
+
partitions: resourceMetadata.partitions || versionData.partitions || {},
|
|
10869
|
+
paranoid: versionData.paranoid !== void 0 ? versionData.paranoid : true,
|
|
10870
|
+
allNestedObjectsOptional: versionData.allNestedObjectsOptional !== void 0 ? versionData.allNestedObjectsOptional : true,
|
|
10871
|
+
autoDecrypt: versionData.autoDecrypt !== void 0 ? versionData.autoDecrypt : true,
|
|
10872
|
+
hooks: versionData.hooks || {}
|
|
10645
10873
|
});
|
|
10646
10874
|
}
|
|
10647
10875
|
}
|
|
@@ -10779,15 +11007,14 @@ class Database extends EventEmitter {
|
|
|
10779
11007
|
[version]: {
|
|
10780
11008
|
hash: definitionHash,
|
|
10781
11009
|
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
|
-
},
|
|
10790
11010
|
behavior: resourceDef.behavior || "user-management",
|
|
11011
|
+
timestamps: resource.config.timestamps,
|
|
11012
|
+
partitions: resource.config.partitions,
|
|
11013
|
+
paranoid: resource.config.paranoid,
|
|
11014
|
+
allNestedObjectsOptional: resource.config.allNestedObjectsOptional,
|
|
11015
|
+
autoDecrypt: resource.config.autoDecrypt,
|
|
11016
|
+
cache: resource.config.cache,
|
|
11017
|
+
hooks: resource.config.hooks,
|
|
10791
11018
|
createdAt: isNewVersion ? (/* @__PURE__ */ new Date()).toISOString() : existingVersionData?.createdAt
|
|
10792
11019
|
}
|
|
10793
11020
|
}
|
|
@@ -10822,73 +11049,58 @@ class Database extends EventEmitter {
|
|
|
10822
11049
|
}
|
|
10823
11050
|
/**
|
|
10824
11051
|
* Check if a resource exists with the same definition hash
|
|
10825
|
-
* @param {
|
|
10826
|
-
* @param {
|
|
10827
|
-
* @param {Object}
|
|
10828
|
-
* @param {string} behavior - Resource behavior
|
|
10829
|
-
* @
|
|
11052
|
+
* @param {Object} config - Resource configuration
|
|
11053
|
+
* @param {string} config.name - Resource name
|
|
11054
|
+
* @param {Object} config.attributes - Resource attributes
|
|
11055
|
+
* @param {string} [config.behavior] - Resource behavior
|
|
11056
|
+
* @param {Object} [config.options] - Resource options (deprecated, use root level parameters)
|
|
11057
|
+
* @returns {Object} Result with exists and hash information
|
|
10830
11058
|
*/
|
|
10831
|
-
resourceExistsWithSameHash({ name, attributes,
|
|
11059
|
+
resourceExistsWithSameHash({ name, attributes, behavior = "user-management", options = {} }) {
|
|
10832
11060
|
if (!this.resources[name]) {
|
|
10833
11061
|
return { exists: false, sameHash: false, hash: null };
|
|
10834
11062
|
}
|
|
10835
|
-
const
|
|
11063
|
+
const existingResource = this.resources[name];
|
|
11064
|
+
const existingHash = this.generateDefinitionHash(existingResource.export());
|
|
11065
|
+
const mockResource = new Resource({
|
|
10836
11066
|
name,
|
|
10837
11067
|
attributes,
|
|
10838
11068
|
behavior,
|
|
10839
|
-
observers: [],
|
|
10840
11069
|
client: this.client,
|
|
10841
|
-
version:
|
|
11070
|
+
version: existingResource.version,
|
|
10842
11071
|
passphrase: this.passphrase,
|
|
10843
|
-
cache: this.cache,
|
|
10844
11072
|
...options
|
|
10845
11073
|
});
|
|
10846
|
-
const newHash = this.generateDefinitionHash(
|
|
10847
|
-
const existingHash = this.generateDefinitionHash(this.resources[name].export(), this.resources[name].behavior);
|
|
11074
|
+
const newHash = this.generateDefinitionHash(mockResource.export());
|
|
10848
11075
|
return {
|
|
10849
11076
|
exists: true,
|
|
10850
|
-
sameHash:
|
|
11077
|
+
sameHash: existingHash === newHash,
|
|
10851
11078
|
hash: newHash,
|
|
10852
11079
|
existingHash
|
|
10853
11080
|
};
|
|
10854
11081
|
}
|
|
10855
|
-
|
|
10856
|
-
* Create a resource only if it doesn't exist with the same definition hash
|
|
10857
|
-
* @param {Object} params - Resource parameters
|
|
10858
|
-
* @param {string} params.name - Resource name
|
|
10859
|
-
* @param {Object} params.attributes - Resource attributes
|
|
10860
|
-
* @param {Object} params.options - Resource options
|
|
10861
|
-
* @param {string} params.behavior - Resource behavior
|
|
10862
|
-
* @returns {Object} Object with resource and created flag
|
|
10863
|
-
*/
|
|
10864
|
-
async createResourceIfNotExists({ name, attributes, options = {}, behavior = "user-management" }) {
|
|
10865
|
-
const alreadyExists = !!this.resources[name];
|
|
10866
|
-
const hashCheck = this.resourceExistsWithSameHash({ name, attributes, options, behavior });
|
|
10867
|
-
if (hashCheck.exists && hashCheck.sameHash) {
|
|
10868
|
-
return {
|
|
10869
|
-
resource: this.resources[name],
|
|
10870
|
-
created: false,
|
|
10871
|
-
reason: "Resource already exists with same definition hash"
|
|
10872
|
-
};
|
|
10873
|
-
}
|
|
10874
|
-
const resource = await this.createResource({ name, attributes, options, behavior });
|
|
10875
|
-
return {
|
|
10876
|
-
resource,
|
|
10877
|
-
created: !alreadyExists,
|
|
10878
|
-
reason: alreadyExists ? "Resource updated with new definition" : "New resource created"
|
|
10879
|
-
};
|
|
10880
|
-
}
|
|
10881
|
-
async createResource({ name, attributes, options = {}, behavior = "user-management" }) {
|
|
11082
|
+
async createResource({ name, attributes, behavior = "user-management", hooks, ...config }) {
|
|
10882
11083
|
if (this.resources[name]) {
|
|
10883
11084
|
const existingResource = this.resources[name];
|
|
10884
11085
|
Object.assign(existingResource.config, {
|
|
10885
11086
|
cache: this.cache,
|
|
10886
|
-
...
|
|
11087
|
+
...config
|
|
10887
11088
|
});
|
|
10888
11089
|
if (behavior) {
|
|
10889
11090
|
existingResource.behavior = behavior;
|
|
10890
11091
|
}
|
|
10891
11092
|
existingResource.updateAttributes(attributes);
|
|
11093
|
+
if (hooks) {
|
|
11094
|
+
for (const [event, hooksArr] of Object.entries(hooks)) {
|
|
11095
|
+
if (Array.isArray(hooksArr) && existingResource.hooks[event]) {
|
|
11096
|
+
for (const fn of hooksArr) {
|
|
11097
|
+
if (typeof fn === "function") {
|
|
11098
|
+
existingResource.hooks[event].push(fn.bind(existingResource));
|
|
11099
|
+
}
|
|
11100
|
+
}
|
|
11101
|
+
}
|
|
11102
|
+
}
|
|
11103
|
+
}
|
|
10892
11104
|
const newHash = this.generateDefinitionHash(existingResource.export(), existingResource.behavior);
|
|
10893
11105
|
const existingMetadata2 = this.savedMetadata?.resources?.[name];
|
|
10894
11106
|
const currentVersion = existingMetadata2?.currentVersion || "v0";
|
|
@@ -10910,7 +11122,8 @@ class Database extends EventEmitter {
|
|
|
10910
11122
|
version,
|
|
10911
11123
|
passphrase: this.passphrase,
|
|
10912
11124
|
cache: this.cache,
|
|
10913
|
-
|
|
11125
|
+
hooks,
|
|
11126
|
+
...config
|
|
10914
11127
|
});
|
|
10915
11128
|
this.resources[name] = resource;
|
|
10916
11129
|
await this.uploadMetadataFile();
|
|
@@ -17612,4 +17825,4 @@ class CachePlugin extends Plugin {
|
|
|
17612
17825
|
}
|
|
17613
17826
|
}
|
|
17614
17827
|
|
|
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,
|
|
17828
|
+
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 };
|