s3db.js 11.2.4 → 11.2.5
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-cli.js +588 -74
- package/dist/s3db.cjs.js +1296 -23
- package/dist/s3db.cjs.js.map +1 -1
- package/dist/s3db.es.js +1294 -24
- package/dist/s3db.es.js.map +1 -1
- package/package.json +2 -1
- package/src/concerns/base62.js +70 -0
- package/src/plugins/index.js +1 -0
- package/src/plugins/vector/distances.js +173 -0
- package/src/plugins/vector/kmeans.js +367 -0
- package/src/plugins/vector/metrics.js +369 -0
- package/src/plugins/vector/vector-error.js +43 -0
- package/src/plugins/vector.plugin.js +687 -0
- package/src/schema.class.js +232 -41
- package/src/validator.class.js +8 -0
package/dist/s3db-cli.js
CHANGED
|
@@ -44419,14 +44419,14 @@ function tryFnSync(fn) {
|
|
|
44419
44419
|
}
|
|
44420
44420
|
|
|
44421
44421
|
class BaseError extends Error {
|
|
44422
|
-
constructor({ verbose, bucket, key, message, code, statusCode, requestId, awsMessage, original, commandName, commandInput, metadata,
|
|
44422
|
+
constructor({ verbose, bucket, key, message, code, statusCode, requestId, awsMessage, original, commandName, commandInput, metadata, description, ...rest }) {
|
|
44423
44423
|
if (verbose) message = message + `\n\nVerbose:\n\n${JSON.stringify(rest, null, 2)}`;
|
|
44424
44424
|
super(message);
|
|
44425
44425
|
|
|
44426
44426
|
if (typeof Error.captureStackTrace === 'function') {
|
|
44427
44427
|
Error.captureStackTrace(this, this.constructor);
|
|
44428
|
-
} else {
|
|
44429
|
-
this.stack = (new Error(message)).stack;
|
|
44428
|
+
} else {
|
|
44429
|
+
this.stack = (new Error(message)).stack;
|
|
44430
44430
|
}
|
|
44431
44431
|
|
|
44432
44432
|
super.name = this.constructor.name;
|
|
@@ -44442,7 +44442,7 @@ class BaseError extends Error {
|
|
|
44442
44442
|
this.commandName = commandName;
|
|
44443
44443
|
this.commandInput = commandInput;
|
|
44444
44444
|
this.metadata = metadata;
|
|
44445
|
-
this.
|
|
44445
|
+
this.description = description;
|
|
44446
44446
|
this.data = { bucket, key, ...rest, verbose, message };
|
|
44447
44447
|
}
|
|
44448
44448
|
|
|
@@ -44460,7 +44460,7 @@ class BaseError extends Error {
|
|
|
44460
44460
|
commandName: this.commandName,
|
|
44461
44461
|
commandInput: this.commandInput,
|
|
44462
44462
|
metadata: this.metadata,
|
|
44463
|
-
|
|
44463
|
+
description: this.description,
|
|
44464
44464
|
data: this.data,
|
|
44465
44465
|
original: this.original,
|
|
44466
44466
|
stack: this.stack,
|
|
@@ -44489,6 +44489,14 @@ class S3dbError extends BaseError {
|
|
|
44489
44489
|
}
|
|
44490
44490
|
}
|
|
44491
44491
|
|
|
44492
|
+
// Database operation errors
|
|
44493
|
+
class DatabaseError extends S3dbError {
|
|
44494
|
+
constructor(message, details = {}) {
|
|
44495
|
+
super(message, details);
|
|
44496
|
+
Object.assign(this, details);
|
|
44497
|
+
}
|
|
44498
|
+
}
|
|
44499
|
+
|
|
44492
44500
|
// Validation errors
|
|
44493
44501
|
class ValidationError extends S3dbError {
|
|
44494
44502
|
constructor(message, details = {}) {
|
|
@@ -44580,26 +44588,26 @@ function mapAwsError(err, context = {}) {
|
|
|
44580
44588
|
const metadata = err.$metadata ? { ...err.$metadata } : undefined;
|
|
44581
44589
|
const commandName = context.commandName;
|
|
44582
44590
|
const commandInput = context.commandInput;
|
|
44583
|
-
let
|
|
44591
|
+
let description;
|
|
44584
44592
|
if (code === 'NoSuchKey' || code === 'NotFound') {
|
|
44585
|
-
|
|
44586
|
-
return new NoSuchKey({ ...context, original: err, metadata, commandName, commandInput,
|
|
44593
|
+
description = 'The specified key does not exist in the bucket. Check if the key exists and if your credentials have permission to access it.';
|
|
44594
|
+
return new NoSuchKey({ ...context, original: err, metadata, commandName, commandInput, description });
|
|
44587
44595
|
}
|
|
44588
44596
|
if (code === 'NoSuchBucket') {
|
|
44589
|
-
|
|
44590
|
-
return new NoSuchBucket({ ...context, original: err, metadata, commandName, commandInput,
|
|
44597
|
+
description = 'The specified bucket does not exist. Check if the bucket name is correct and if your credentials have permission to access it.';
|
|
44598
|
+
return new NoSuchBucket({ ...context, original: err, metadata, commandName, commandInput, description });
|
|
44591
44599
|
}
|
|
44592
44600
|
if (code === 'AccessDenied' || (err.statusCode === 403) || code === 'Forbidden') {
|
|
44593
|
-
|
|
44594
|
-
return new PermissionError('Access denied', { ...context, original: err, metadata, commandName, commandInput,
|
|
44601
|
+
description = 'Access denied. Check your AWS credentials, IAM permissions, and bucket policy.';
|
|
44602
|
+
return new PermissionError('Access denied', { ...context, original: err, metadata, commandName, commandInput, description });
|
|
44595
44603
|
}
|
|
44596
44604
|
if (code === 'ValidationError' || (err.statusCode === 400)) {
|
|
44597
|
-
|
|
44598
|
-
return new ValidationError('Validation error', { ...context, original: err, metadata, commandName, commandInput,
|
|
44605
|
+
description = 'Validation error. Check the request parameters and payload format.';
|
|
44606
|
+
return new ValidationError('Validation error', { ...context, original: err, metadata, commandName, commandInput, description });
|
|
44599
44607
|
}
|
|
44600
44608
|
if (code === 'MissingMetadata') {
|
|
44601
|
-
|
|
44602
|
-
return new MissingMetadata({ ...context, original: err, metadata, commandName, commandInput,
|
|
44609
|
+
description = 'Object metadata is missing or invalid. Check if the object was uploaded correctly.';
|
|
44610
|
+
return new MissingMetadata({ ...context, original: err, metadata, commandName, commandInput, description });
|
|
44603
44611
|
}
|
|
44604
44612
|
// Outros mapeamentos podem ser adicionados aqui
|
|
44605
44613
|
// Incluir detalhes do erro original para facilitar debug
|
|
@@ -44609,39 +44617,209 @@ function mapAwsError(err, context = {}) {
|
|
|
44609
44617
|
err.statusCode && `Status: ${err.statusCode}`,
|
|
44610
44618
|
err.stack && `Stack: ${err.stack.split('\n')[0]}`,
|
|
44611
44619
|
].filter(Boolean).join(' | ');
|
|
44612
|
-
|
|
44613
|
-
|
|
44614
|
-
return new UnknownError(errorDetails, { ...context, original: err, metadata, commandName, commandInput,
|
|
44620
|
+
|
|
44621
|
+
description = `Check the error details and AWS documentation. Original error: ${err.message || err.toString()}`;
|
|
44622
|
+
return new UnknownError(errorDetails, { ...context, original: err, metadata, commandName, commandInput, description });
|
|
44615
44623
|
}
|
|
44616
44624
|
|
|
44617
44625
|
class ConnectionStringError extends S3dbError {
|
|
44618
44626
|
constructor(message, details = {}) {
|
|
44619
|
-
|
|
44627
|
+
const description = details.description || 'Invalid connection string format. Check the connection string syntax and credentials.';
|
|
44628
|
+
super(message, { ...details, description });
|
|
44620
44629
|
}
|
|
44621
44630
|
}
|
|
44622
44631
|
|
|
44623
44632
|
class CryptoError extends S3dbError {
|
|
44624
44633
|
constructor(message, details = {}) {
|
|
44625
|
-
|
|
44634
|
+
const description = details.description || 'Cryptography operation failed. Check if the crypto library is available and input is valid.';
|
|
44635
|
+
super(message, { ...details, description });
|
|
44626
44636
|
}
|
|
44627
44637
|
}
|
|
44628
44638
|
|
|
44629
44639
|
class SchemaError extends S3dbError {
|
|
44630
44640
|
constructor(message, details = {}) {
|
|
44631
|
-
|
|
44641
|
+
const description = details.description || 'Schema validation failed. Check schema definition and input data format.';
|
|
44642
|
+
super(message, { ...details, description });
|
|
44632
44643
|
}
|
|
44633
44644
|
}
|
|
44634
44645
|
|
|
44635
44646
|
class ResourceError extends S3dbError {
|
|
44636
44647
|
constructor(message, details = {}) {
|
|
44637
|
-
|
|
44648
|
+
const description = details.description || 'Resource operation failed. Check resource configuration, attributes, and operation context.';
|
|
44649
|
+
super(message, { ...details, description });
|
|
44638
44650
|
Object.assign(this, details);
|
|
44639
44651
|
}
|
|
44640
44652
|
}
|
|
44641
44653
|
|
|
44642
44654
|
class PartitionError extends S3dbError {
|
|
44643
44655
|
constructor(message, details = {}) {
|
|
44644
|
-
|
|
44656
|
+
// Generate description if not provided
|
|
44657
|
+
let description = details.description;
|
|
44658
|
+
if (!description && details.resourceName && details.partitionName && details.fieldName) {
|
|
44659
|
+
const { resourceName, partitionName, fieldName, availableFields = [] } = details;
|
|
44660
|
+
description = `
|
|
44661
|
+
Partition Field Validation Error
|
|
44662
|
+
|
|
44663
|
+
Resource: ${resourceName}
|
|
44664
|
+
Partition: ${partitionName}
|
|
44665
|
+
Missing Field: ${fieldName}
|
|
44666
|
+
|
|
44667
|
+
Available fields in schema:
|
|
44668
|
+
${availableFields.map(f => ` • ${f}`).join('\n') || ' (no fields defined)'}
|
|
44669
|
+
|
|
44670
|
+
Possible causes:
|
|
44671
|
+
1. Field was removed from schema but partition still references it
|
|
44672
|
+
2. Typo in partition field name
|
|
44673
|
+
3. Nested field path is incorrect (use dot notation like 'utm.source')
|
|
44674
|
+
|
|
44675
|
+
Solution:
|
|
44676
|
+
${details.strictValidation === false
|
|
44677
|
+
? ' • Update partition definition to use existing fields'
|
|
44678
|
+
: ` • Add missing field to schema, OR
|
|
44679
|
+
• Update partition definition to use existing fields, OR
|
|
44680
|
+
• Use strictValidation: false to skip this check during testing`}
|
|
44681
|
+
|
|
44682
|
+
Docs: https://github.com/forattini-dev/s3db.js/blob/main/docs/README.md#partitions
|
|
44683
|
+
`.trim();
|
|
44684
|
+
}
|
|
44685
|
+
|
|
44686
|
+
super(message, {
|
|
44687
|
+
...details,
|
|
44688
|
+
description
|
|
44689
|
+
});
|
|
44690
|
+
}
|
|
44691
|
+
}
|
|
44692
|
+
|
|
44693
|
+
// Behavior errors
|
|
44694
|
+
class BehaviorError extends S3dbError {
|
|
44695
|
+
constructor(message, details = {}) {
|
|
44696
|
+
const {
|
|
44697
|
+
behavior = 'unknown',
|
|
44698
|
+
availableBehaviors = [],
|
|
44699
|
+
...rest
|
|
44700
|
+
} = details;
|
|
44701
|
+
|
|
44702
|
+
let description = details.description;
|
|
44703
|
+
if (!description) {
|
|
44704
|
+
description = `
|
|
44705
|
+
Behavior Error
|
|
44706
|
+
|
|
44707
|
+
Requested: ${behavior}
|
|
44708
|
+
Available: ${availableBehaviors.join(', ') || 'body-overflow, body-only, truncate-data, enforce-limits, user-managed'}
|
|
44709
|
+
|
|
44710
|
+
Possible causes:
|
|
44711
|
+
1. Behavior name misspelled
|
|
44712
|
+
2. Custom behavior not registered
|
|
44713
|
+
|
|
44714
|
+
Solution:
|
|
44715
|
+
Use one of the available behaviors or register custom behavior.
|
|
44716
|
+
|
|
44717
|
+
Docs: https://github.com/forattini-dev/s3db.js/blob/main/docs/README.md#behaviors
|
|
44718
|
+
`.trim();
|
|
44719
|
+
}
|
|
44720
|
+
|
|
44721
|
+
super(message, {
|
|
44722
|
+
...rest,
|
|
44723
|
+
behavior,
|
|
44724
|
+
availableBehaviors,
|
|
44725
|
+
description
|
|
44726
|
+
});
|
|
44727
|
+
}
|
|
44728
|
+
}
|
|
44729
|
+
|
|
44730
|
+
// Stream errors
|
|
44731
|
+
class StreamError extends S3dbError {
|
|
44732
|
+
constructor(message, details = {}) {
|
|
44733
|
+
const {
|
|
44734
|
+
operation = 'unknown',
|
|
44735
|
+
resource,
|
|
44736
|
+
...rest
|
|
44737
|
+
} = details;
|
|
44738
|
+
|
|
44739
|
+
let description = details.description;
|
|
44740
|
+
if (!description) {
|
|
44741
|
+
description = `
|
|
44742
|
+
Stream Error
|
|
44743
|
+
|
|
44744
|
+
Operation: ${operation}
|
|
44745
|
+
${resource ? `Resource: ${resource}` : ''}
|
|
44746
|
+
|
|
44747
|
+
Possible causes:
|
|
44748
|
+
1. Stream not properly initialized
|
|
44749
|
+
2. Resource not available
|
|
44750
|
+
3. Network error during streaming
|
|
44751
|
+
|
|
44752
|
+
Solution:
|
|
44753
|
+
Check stream configuration and resource availability.
|
|
44754
|
+
|
|
44755
|
+
Docs: https://github.com/forattini-dev/s3db.js/blob/main/docs/README.md#streaming
|
|
44756
|
+
`.trim();
|
|
44757
|
+
}
|
|
44758
|
+
|
|
44759
|
+
super(message, {
|
|
44760
|
+
...rest,
|
|
44761
|
+
operation,
|
|
44762
|
+
resource,
|
|
44763
|
+
description
|
|
44764
|
+
});
|
|
44765
|
+
}
|
|
44766
|
+
}
|
|
44767
|
+
|
|
44768
|
+
// Metadata limit errors (specific for 2KB S3 limit)
|
|
44769
|
+
class MetadataLimitError extends S3dbError {
|
|
44770
|
+
constructor(message, details = {}) {
|
|
44771
|
+
const {
|
|
44772
|
+
totalSize,
|
|
44773
|
+
effectiveLimit,
|
|
44774
|
+
absoluteLimit = 2047,
|
|
44775
|
+
excess,
|
|
44776
|
+
resourceName,
|
|
44777
|
+
operation,
|
|
44778
|
+
...rest
|
|
44779
|
+
} = details;
|
|
44780
|
+
|
|
44781
|
+
let description = details.description;
|
|
44782
|
+
if (!description && totalSize && effectiveLimit) {
|
|
44783
|
+
description = `
|
|
44784
|
+
S3 Metadata Size Limit Exceeded
|
|
44785
|
+
|
|
44786
|
+
Current Size: ${totalSize} bytes
|
|
44787
|
+
Effective Limit: ${effectiveLimit} bytes
|
|
44788
|
+
Absolute Limit: ${absoluteLimit} bytes
|
|
44789
|
+
${excess ? `Excess: ${excess} bytes` : ''}
|
|
44790
|
+
${resourceName ? `Resource: ${resourceName}` : ''}
|
|
44791
|
+
${operation ? `Operation: ${operation}` : ''}
|
|
44792
|
+
|
|
44793
|
+
S3 has a hard limit of 2KB (2047 bytes) for object metadata.
|
|
44794
|
+
|
|
44795
|
+
Solutions:
|
|
44796
|
+
1. Use 'body-overflow' behavior to store excess in body
|
|
44797
|
+
2. Use 'body-only' behavior to store everything in body
|
|
44798
|
+
3. Reduce number of fields
|
|
44799
|
+
4. Use shorter field values
|
|
44800
|
+
5. Enable advanced metadata encoding
|
|
44801
|
+
|
|
44802
|
+
Example:
|
|
44803
|
+
await db.createResource({
|
|
44804
|
+
name: '${resourceName || 'myResource'}',
|
|
44805
|
+
behavior: 'body-overflow', // Automatically handles overflow
|
|
44806
|
+
attributes: { ... }
|
|
44807
|
+
});
|
|
44808
|
+
|
|
44809
|
+
Docs: https://github.com/forattini-dev/s3db.js/blob/main/docs/README.md#metadata-size-limits
|
|
44810
|
+
`.trim();
|
|
44811
|
+
}
|
|
44812
|
+
|
|
44813
|
+
super(message, {
|
|
44814
|
+
...rest,
|
|
44815
|
+
totalSize,
|
|
44816
|
+
effectiveLimit,
|
|
44817
|
+
absoluteLimit,
|
|
44818
|
+
excess,
|
|
44819
|
+
resourceName,
|
|
44820
|
+
operation,
|
|
44821
|
+
description
|
|
44822
|
+
});
|
|
44645
44823
|
}
|
|
44646
44824
|
}
|
|
44647
44825
|
|
|
@@ -45621,7 +45799,17 @@ class Client extends EventEmitter {
|
|
|
45621
45799
|
});
|
|
45622
45800
|
this.emit("moveAllObjects", { results, errors }, { prefixFrom, prefixTo });
|
|
45623
45801
|
if (errors.length > 0) {
|
|
45624
|
-
throw new
|
|
45802
|
+
throw new UnknownError("Some objects could not be moved", {
|
|
45803
|
+
bucket: this.config.bucket,
|
|
45804
|
+
operation: 'moveAllObjects',
|
|
45805
|
+
prefixFrom,
|
|
45806
|
+
prefixTo,
|
|
45807
|
+
totalKeys: keys.length,
|
|
45808
|
+
failedCount: errors.length,
|
|
45809
|
+
successCount: results.length,
|
|
45810
|
+
errors: errors.map(e => ({ message: e.message, raw: e.raw })),
|
|
45811
|
+
suggestion: 'Check S3 permissions and retry failed objects individually'
|
|
45812
|
+
});
|
|
45625
45813
|
}
|
|
45626
45814
|
return results;
|
|
45627
45815
|
}
|
|
@@ -48287,6 +48475,14 @@ class Validator extends FastestValidator {
|
|
|
48287
48475
|
type: "any",
|
|
48288
48476
|
custom: this.autoEncrypt ? jsonHandler : undefined,
|
|
48289
48477
|
});
|
|
48478
|
+
|
|
48479
|
+
// Embedding type - shorthand for arrays of numbers optimized for embeddings
|
|
48480
|
+
// Usage: 'embedding:1536' or 'embedding|length:768'
|
|
48481
|
+
this.alias('embedding', {
|
|
48482
|
+
type: "array",
|
|
48483
|
+
items: "number",
|
|
48484
|
+
empty: false,
|
|
48485
|
+
});
|
|
48290
48486
|
}
|
|
48291
48487
|
}
|
|
48292
48488
|
|
|
@@ -48361,6 +48557,76 @@ const decodeDecimal = s => {
|
|
|
48361
48557
|
return negative ? -num : num;
|
|
48362
48558
|
};
|
|
48363
48559
|
|
|
48560
|
+
/**
|
|
48561
|
+
* Fixed-point encoding optimized for normalized values (typically -1 to 1)
|
|
48562
|
+
* Common in embeddings, similarity scores, probabilities, etc.
|
|
48563
|
+
*
|
|
48564
|
+
* Achieves ~77% compression vs encodeDecimal for embedding vectors.
|
|
48565
|
+
*
|
|
48566
|
+
* @param {number} n - Number to encode (works for any range, optimized for [-1, 1])
|
|
48567
|
+
* @param {number} precision - Decimal places to preserve (default: 6)
|
|
48568
|
+
* @returns {string} Base62-encoded string with '^' prefix to indicate fixed-point encoding
|
|
48569
|
+
*
|
|
48570
|
+
* Examples:
|
|
48571
|
+
* 0.123456 → "^w7f" (4 bytes vs 8 bytes with encodeDecimal)
|
|
48572
|
+
* -0.8234567 → "^-3sdz" (6 bytes vs 10 bytes)
|
|
48573
|
+
* 1.5 → "^98v9" (for values outside [-1,1], still works but less optimal)
|
|
48574
|
+
*/
|
|
48575
|
+
const encodeFixedPoint = (n, precision = 6) => {
|
|
48576
|
+
if (typeof n !== 'number' || isNaN(n)) return 'undefined';
|
|
48577
|
+
if (!isFinite(n)) return 'undefined';
|
|
48578
|
+
|
|
48579
|
+
const scale = Math.pow(10, precision);
|
|
48580
|
+
const scaled = Math.round(n * scale);
|
|
48581
|
+
|
|
48582
|
+
if (scaled === 0) return '^0';
|
|
48583
|
+
|
|
48584
|
+
const negative = scaled < 0;
|
|
48585
|
+
let num = Math.abs(scaled);
|
|
48586
|
+
let s = '';
|
|
48587
|
+
|
|
48588
|
+
while (num > 0) {
|
|
48589
|
+
s = alphabet[num % base] + s;
|
|
48590
|
+
num = Math.floor(num / base);
|
|
48591
|
+
}
|
|
48592
|
+
|
|
48593
|
+
// Prefix with ^ to distinguish from regular base62
|
|
48594
|
+
return '^' + (negative ? '-' : '') + s;
|
|
48595
|
+
};
|
|
48596
|
+
|
|
48597
|
+
/**
|
|
48598
|
+
* Decodes fixed-point encoded values
|
|
48599
|
+
*
|
|
48600
|
+
* @param {string} s - Encoded string (must start with '^')
|
|
48601
|
+
* @param {number} precision - Decimal places used in encoding (default: 6)
|
|
48602
|
+
* @returns {number} Decoded number
|
|
48603
|
+
*/
|
|
48604
|
+
const decodeFixedPoint = (s, precision = 6) => {
|
|
48605
|
+
if (typeof s !== 'string') return NaN;
|
|
48606
|
+
if (!s.startsWith('^')) return NaN; // Safety check
|
|
48607
|
+
|
|
48608
|
+
s = s.slice(1); // Remove ^ prefix
|
|
48609
|
+
|
|
48610
|
+
if (s === '0') return 0;
|
|
48611
|
+
|
|
48612
|
+
let negative = false;
|
|
48613
|
+
if (s[0] === '-') {
|
|
48614
|
+
negative = true;
|
|
48615
|
+
s = s.slice(1);
|
|
48616
|
+
}
|
|
48617
|
+
|
|
48618
|
+
let r = 0;
|
|
48619
|
+
for (let i = 0; i < s.length; i++) {
|
|
48620
|
+
const idx = charToValue[s[i]];
|
|
48621
|
+
if (idx === undefined) return NaN;
|
|
48622
|
+
r = r * base + idx;
|
|
48623
|
+
}
|
|
48624
|
+
|
|
48625
|
+
const scale = Math.pow(10, precision);
|
|
48626
|
+
const scaled = negative ? -r : r;
|
|
48627
|
+
return scaled / scale;
|
|
48628
|
+
};
|
|
48629
|
+
|
|
48364
48630
|
/**
|
|
48365
48631
|
* Generate base62 mapping for attributes
|
|
48366
48632
|
* @param {string[]} keys - Array of attribute keys
|
|
@@ -48618,6 +48884,60 @@ const SchemaActions = {
|
|
|
48618
48884
|
return NaN;
|
|
48619
48885
|
});
|
|
48620
48886
|
},
|
|
48887
|
+
fromArrayOfEmbeddings: (value, { separator, precision = 6 }) => {
|
|
48888
|
+
if (value === null || value === undefined || !Array.isArray(value)) {
|
|
48889
|
+
return value;
|
|
48890
|
+
}
|
|
48891
|
+
if (value.length === 0) {
|
|
48892
|
+
return '';
|
|
48893
|
+
}
|
|
48894
|
+
const encodedItems = value.map(item => {
|
|
48895
|
+
if (typeof item === 'number' && !isNaN(item)) {
|
|
48896
|
+
return encodeFixedPoint(item, precision);
|
|
48897
|
+
}
|
|
48898
|
+
// fallback: try to parse as number, else keep as is
|
|
48899
|
+
const n = Number(item);
|
|
48900
|
+
return isNaN(n) ? '' : encodeFixedPoint(n, precision);
|
|
48901
|
+
});
|
|
48902
|
+
return encodedItems.join(separator);
|
|
48903
|
+
},
|
|
48904
|
+
toArrayOfEmbeddings: (value, { separator, precision = 6 }) => {
|
|
48905
|
+
if (Array.isArray(value)) {
|
|
48906
|
+
return value.map(v => (typeof v === 'number' ? v : decodeFixedPoint(v, precision)));
|
|
48907
|
+
}
|
|
48908
|
+
if (value === null || value === undefined) {
|
|
48909
|
+
return value;
|
|
48910
|
+
}
|
|
48911
|
+
if (value === '') {
|
|
48912
|
+
return [];
|
|
48913
|
+
}
|
|
48914
|
+
const str = String(value);
|
|
48915
|
+
const items = [];
|
|
48916
|
+
let current = '';
|
|
48917
|
+
let i = 0;
|
|
48918
|
+
while (i < str.length) {
|
|
48919
|
+
if (str[i] === '\\' && i + 1 < str.length) {
|
|
48920
|
+
current += str[i + 1];
|
|
48921
|
+
i += 2;
|
|
48922
|
+
} else if (str[i] === separator) {
|
|
48923
|
+
items.push(current);
|
|
48924
|
+
current = '';
|
|
48925
|
+
i++;
|
|
48926
|
+
} else {
|
|
48927
|
+
current += str[i];
|
|
48928
|
+
i++;
|
|
48929
|
+
}
|
|
48930
|
+
}
|
|
48931
|
+
items.push(current);
|
|
48932
|
+
return items.map(v => {
|
|
48933
|
+
if (typeof v === 'number') return v;
|
|
48934
|
+
if (typeof v === 'string' && v !== '') {
|
|
48935
|
+
const n = decodeFixedPoint(v, precision);
|
|
48936
|
+
return isNaN(n) ? NaN : n;
|
|
48937
|
+
}
|
|
48938
|
+
return NaN;
|
|
48939
|
+
});
|
|
48940
|
+
},
|
|
48621
48941
|
|
|
48622
48942
|
};
|
|
48623
48943
|
|
|
@@ -48695,16 +49015,16 @@ class Schema {
|
|
|
48695
49015
|
|
|
48696
49016
|
extractObjectKeys(obj, prefix = '') {
|
|
48697
49017
|
const objectKeys = [];
|
|
48698
|
-
|
|
49018
|
+
|
|
48699
49019
|
for (const [key, value] of Object.entries(obj)) {
|
|
48700
49020
|
if (key.startsWith('$$')) continue; // Skip schema metadata
|
|
48701
|
-
|
|
49021
|
+
|
|
48702
49022
|
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
48703
|
-
|
|
49023
|
+
|
|
48704
49024
|
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
48705
49025
|
// This is an object, add its key
|
|
48706
49026
|
objectKeys.push(fullKey);
|
|
48707
|
-
|
|
49027
|
+
|
|
48708
49028
|
// Check if it has nested objects
|
|
48709
49029
|
if (value.$$type === 'object') {
|
|
48710
49030
|
// Recursively extract nested object keys
|
|
@@ -48712,31 +49032,135 @@ class Schema {
|
|
|
48712
49032
|
}
|
|
48713
49033
|
}
|
|
48714
49034
|
}
|
|
48715
|
-
|
|
49035
|
+
|
|
48716
49036
|
return objectKeys;
|
|
48717
49037
|
}
|
|
48718
49038
|
|
|
49039
|
+
_generateHooksFromOriginalAttributes(attributes, prefix = '') {
|
|
49040
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
49041
|
+
if (key.startsWith('$$')) continue;
|
|
49042
|
+
|
|
49043
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
49044
|
+
|
|
49045
|
+
// Check if this is an object notation type definition (has 'type' property)
|
|
49046
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value) && value.type) {
|
|
49047
|
+
if (value.type === 'array' && value.items) {
|
|
49048
|
+
// Handle array with object notation
|
|
49049
|
+
const itemsType = value.items;
|
|
49050
|
+
const arrayLength = typeof value.length === 'number' ? value.length : null;
|
|
49051
|
+
|
|
49052
|
+
if (itemsType === 'string' || (typeof itemsType === 'string' && itemsType.includes('string'))) {
|
|
49053
|
+
this.addHook("beforeMap", fullKey, "fromArray");
|
|
49054
|
+
this.addHook("afterUnmap", fullKey, "toArray");
|
|
49055
|
+
} else if (itemsType === 'number' || (typeof itemsType === 'string' && itemsType.includes('number'))) {
|
|
49056
|
+
const isIntegerArray = typeof itemsType === 'string' && itemsType.includes('integer');
|
|
49057
|
+
const isEmbedding = !isIntegerArray && arrayLength !== null && arrayLength >= 256;
|
|
49058
|
+
|
|
49059
|
+
if (isIntegerArray) {
|
|
49060
|
+
this.addHook("beforeMap", fullKey, "fromArrayOfNumbers");
|
|
49061
|
+
this.addHook("afterUnmap", fullKey, "toArrayOfNumbers");
|
|
49062
|
+
} else if (isEmbedding) {
|
|
49063
|
+
this.addHook("beforeMap", fullKey, "fromArrayOfEmbeddings");
|
|
49064
|
+
this.addHook("afterUnmap", fullKey, "toArrayOfEmbeddings");
|
|
49065
|
+
} else {
|
|
49066
|
+
this.addHook("beforeMap", fullKey, "fromArrayOfDecimals");
|
|
49067
|
+
this.addHook("afterUnmap", fullKey, "toArrayOfDecimals");
|
|
49068
|
+
}
|
|
49069
|
+
}
|
|
49070
|
+
}
|
|
49071
|
+
// For other types with object notation, they'll be handled by the flattened processing
|
|
49072
|
+
} else if (typeof value === 'object' && value !== null && !Array.isArray(value) && !value.type) {
|
|
49073
|
+
// This is a nested object, recurse
|
|
49074
|
+
this._generateHooksFromOriginalAttributes(value, fullKey);
|
|
49075
|
+
}
|
|
49076
|
+
}
|
|
49077
|
+
}
|
|
49078
|
+
|
|
48719
49079
|
generateAutoHooks() {
|
|
49080
|
+
// First, process the original attributes to find arrays with object notation
|
|
49081
|
+
// This handles cases like: { type: 'array', items: 'number', length: 768 }
|
|
49082
|
+
this._generateHooksFromOriginalAttributes(this.attributes);
|
|
49083
|
+
|
|
49084
|
+
// Then process the flattened schema for other types
|
|
48720
49085
|
const schema = flatten(cloneDeep(this.attributes), { safe: true });
|
|
48721
49086
|
|
|
48722
49087
|
for (const [name, definition] of Object.entries(schema)) {
|
|
48723
|
-
//
|
|
48724
|
-
if (
|
|
48725
|
-
|
|
49088
|
+
// Skip metadata fields
|
|
49089
|
+
if (name.includes('$$')) continue;
|
|
49090
|
+
|
|
49091
|
+
// Skip if hooks already exist (from object notation processing)
|
|
49092
|
+
if (this.options.hooks.beforeMap[name] || this.options.hooks.afterUnmap[name]) {
|
|
49093
|
+
continue;
|
|
49094
|
+
}
|
|
49095
|
+
|
|
49096
|
+
// Normalize definition - can be a string or value from flattened object
|
|
49097
|
+
const defStr = typeof definition === 'string' ? definition : '';
|
|
49098
|
+
const defType = typeof definition === 'object' && definition !== null ? definition.type : null;
|
|
49099
|
+
|
|
49100
|
+
// Check if this is an embedding type (custom shorthand)
|
|
49101
|
+
const isEmbeddingType = defStr.includes("embedding") || defType === 'embedding';
|
|
49102
|
+
|
|
49103
|
+
if (isEmbeddingType) {
|
|
49104
|
+
const lengthMatch = defStr.match(/embedding:(\d+)/);
|
|
49105
|
+
if (lengthMatch) {
|
|
49106
|
+
parseInt(lengthMatch[1], 10);
|
|
49107
|
+
} else if (defStr.includes('length:')) {
|
|
49108
|
+
const match = defStr.match(/length:(\d+)/);
|
|
49109
|
+
if (match) parseInt(match[1], 10);
|
|
49110
|
+
}
|
|
49111
|
+
|
|
49112
|
+
// Embeddings always use fixed-point encoding
|
|
49113
|
+
this.addHook("beforeMap", name, "fromArrayOfEmbeddings");
|
|
49114
|
+
this.addHook("afterUnmap", name, "toArrayOfEmbeddings");
|
|
49115
|
+
continue;
|
|
49116
|
+
}
|
|
49117
|
+
|
|
49118
|
+
// Check if this is an array type
|
|
49119
|
+
const isArray = defStr.includes("array") || defType === 'array';
|
|
49120
|
+
|
|
49121
|
+
if (isArray) {
|
|
49122
|
+
// Determine item type for arrays
|
|
49123
|
+
let itemsType = null;
|
|
49124
|
+
if (typeof definition === 'object' && definition !== null && definition.items) {
|
|
49125
|
+
itemsType = definition.items;
|
|
49126
|
+
} else if (defStr.includes('items:string')) {
|
|
49127
|
+
itemsType = 'string';
|
|
49128
|
+
} else if (defStr.includes('items:number')) {
|
|
49129
|
+
itemsType = 'number';
|
|
49130
|
+
}
|
|
49131
|
+
|
|
49132
|
+
if (itemsType === 'string' || (typeof itemsType === 'string' && itemsType.includes('string'))) {
|
|
48726
49133
|
this.addHook("beforeMap", name, "fromArray");
|
|
48727
49134
|
this.addHook("afterUnmap", name, "toArray");
|
|
48728
|
-
} else if (
|
|
49135
|
+
} else if (itemsType === 'number' || (typeof itemsType === 'string' && itemsType.includes('number'))) {
|
|
48729
49136
|
// Check if the array items should be treated as integers
|
|
48730
|
-
const isIntegerArray =
|
|
48731
|
-
|
|
48732
|
-
|
|
48733
|
-
|
|
49137
|
+
const isIntegerArray = defStr.includes("integer:true") ||
|
|
49138
|
+
defStr.includes("|integer:") ||
|
|
49139
|
+
defStr.includes("|integer") ||
|
|
49140
|
+
(typeof itemsType === 'string' && itemsType.includes('integer'));
|
|
49141
|
+
|
|
49142
|
+
// Check if this is an embedding array (large arrays of decimals)
|
|
49143
|
+
// Common embedding dimensions: 256, 384, 512, 768, 1024, 1536, 2048, 3072
|
|
49144
|
+
let arrayLength = null;
|
|
49145
|
+
if (typeof definition === 'object' && definition !== null && typeof definition.length === 'number') {
|
|
49146
|
+
arrayLength = definition.length;
|
|
49147
|
+
} else if (defStr.includes('length:')) {
|
|
49148
|
+
const match = defStr.match(/length:(\d+)/);
|
|
49149
|
+
if (match) arrayLength = parseInt(match[1], 10);
|
|
49150
|
+
}
|
|
49151
|
+
|
|
49152
|
+
const isEmbedding = !isIntegerArray && arrayLength !== null && arrayLength >= 256;
|
|
49153
|
+
|
|
48734
49154
|
if (isIntegerArray) {
|
|
48735
49155
|
// Use standard base62 for arrays of integers
|
|
48736
49156
|
this.addHook("beforeMap", name, "fromArrayOfNumbers");
|
|
48737
49157
|
this.addHook("afterUnmap", name, "toArrayOfNumbers");
|
|
49158
|
+
} else if (isEmbedding) {
|
|
49159
|
+
// Use fixed-point encoding for embedding vectors (77% compression)
|
|
49160
|
+
this.addHook("beforeMap", name, "fromArrayOfEmbeddings");
|
|
49161
|
+
this.addHook("afterUnmap", name, "toArrayOfEmbeddings");
|
|
48738
49162
|
} else {
|
|
48739
|
-
// Use decimal-aware base62 for arrays of decimals
|
|
49163
|
+
// Use decimal-aware base62 for regular arrays of decimals
|
|
48740
49164
|
this.addHook("beforeMap", name, "fromArrayOfDecimals");
|
|
48741
49165
|
this.addHook("afterUnmap", name, "toArrayOfDecimals");
|
|
48742
49166
|
}
|
|
@@ -48746,7 +49170,7 @@ class Schema {
|
|
|
48746
49170
|
}
|
|
48747
49171
|
|
|
48748
49172
|
// Handle secrets
|
|
48749
|
-
if (
|
|
49173
|
+
if (defStr.includes("secret") || defType === 'secret') {
|
|
48750
49174
|
if (this.options.autoEncrypt) {
|
|
48751
49175
|
this.addHook("beforeMap", name, "encrypt");
|
|
48752
49176
|
}
|
|
@@ -48758,12 +49182,12 @@ class Schema {
|
|
|
48758
49182
|
}
|
|
48759
49183
|
|
|
48760
49184
|
// Handle numbers (only for non-array fields)
|
|
48761
|
-
if (
|
|
49185
|
+
if (defStr.includes("number") || defType === 'number') {
|
|
48762
49186
|
// Check if it's specifically an integer field
|
|
48763
|
-
const isInteger =
|
|
48764
|
-
|
|
48765
|
-
|
|
48766
|
-
|
|
49187
|
+
const isInteger = defStr.includes("integer:true") ||
|
|
49188
|
+
defStr.includes("|integer:") ||
|
|
49189
|
+
defStr.includes("|integer");
|
|
49190
|
+
|
|
48767
49191
|
if (isInteger) {
|
|
48768
49192
|
// Use standard base62 for integers
|
|
48769
49193
|
this.addHook("beforeMap", name, "toBase62");
|
|
@@ -48777,21 +49201,21 @@ class Schema {
|
|
|
48777
49201
|
}
|
|
48778
49202
|
|
|
48779
49203
|
// Handle booleans
|
|
48780
|
-
if (
|
|
49204
|
+
if (defStr.includes("boolean") || defType === 'boolean') {
|
|
48781
49205
|
this.addHook("beforeMap", name, "fromBool");
|
|
48782
49206
|
this.addHook("afterUnmap", name, "toBool");
|
|
48783
49207
|
continue;
|
|
48784
49208
|
}
|
|
48785
49209
|
|
|
48786
49210
|
// Handle JSON fields
|
|
48787
|
-
if (
|
|
49211
|
+
if (defStr.includes("json") || defType === 'json') {
|
|
48788
49212
|
this.addHook("beforeMap", name, "toJSON");
|
|
48789
49213
|
this.addHook("afterUnmap", name, "fromJSON");
|
|
48790
49214
|
continue;
|
|
48791
49215
|
}
|
|
48792
49216
|
|
|
48793
49217
|
// Handle object fields - add JSON serialization hooks
|
|
48794
|
-
if (definition === "object" ||
|
|
49218
|
+
if (definition === "object" || defStr.includes("object") || defType === 'object') {
|
|
48795
49219
|
this.addHook("beforeMap", name, "toJSON");
|
|
48796
49220
|
this.addHook("afterUnmap", name, "fromJSON");
|
|
48797
49221
|
continue;
|
|
@@ -48948,8 +49372,11 @@ class Schema {
|
|
|
48948
49372
|
const originalKey = reversedMap && reversedMap[key] ? reversedMap[key] : key;
|
|
48949
49373
|
let parsedValue = value;
|
|
48950
49374
|
const attrDef = this.getAttributeDefinition(originalKey);
|
|
49375
|
+
const hasAfterUnmapHook = this.options.hooks?.afterUnmap?.[originalKey];
|
|
49376
|
+
|
|
48951
49377
|
// Always unmap base62 strings to numbers for number fields (but not array fields or decimal fields)
|
|
48952
|
-
|
|
49378
|
+
// Skip if there are afterUnmap hooks that will handle the conversion
|
|
49379
|
+
if (!hasAfterUnmapHook && typeof attrDef === 'string' && attrDef.includes('number') && !attrDef.includes('array') && !attrDef.includes('decimal')) {
|
|
48953
49380
|
if (typeof parsedValue === 'string' && parsedValue !== '') {
|
|
48954
49381
|
parsedValue = decode(parsedValue);
|
|
48955
49382
|
} else if (typeof parsedValue === 'number') ; else {
|
|
@@ -49017,26 +49444,54 @@ class Schema {
|
|
|
49017
49444
|
*/
|
|
49018
49445
|
preprocessAttributesForValidation(attributes) {
|
|
49019
49446
|
const processed = {};
|
|
49020
|
-
|
|
49447
|
+
|
|
49021
49448
|
for (const [key, value] of Object.entries(attributes)) {
|
|
49022
|
-
if (typeof value === '
|
|
49023
|
-
|
|
49024
|
-
|
|
49025
|
-
|
|
49026
|
-
|
|
49027
|
-
|
|
49028
|
-
|
|
49029
|
-
|
|
49030
|
-
|
|
49031
|
-
|
|
49032
|
-
|
|
49449
|
+
if (typeof value === 'string') {
|
|
49450
|
+
// Expand embedding:XXX shorthand to array|items:number|length:XXX
|
|
49451
|
+
if (value.startsWith('embedding:')) {
|
|
49452
|
+
const lengthMatch = value.match(/embedding:(\d+)/);
|
|
49453
|
+
if (lengthMatch) {
|
|
49454
|
+
const length = lengthMatch[1];
|
|
49455
|
+
// Extract any additional modifiers after the length
|
|
49456
|
+
const rest = value.substring(`embedding:${length}`.length);
|
|
49457
|
+
processed[key] = `array|items:number|length:${length}|empty:false${rest}`;
|
|
49458
|
+
continue;
|
|
49459
|
+
}
|
|
49460
|
+
}
|
|
49461
|
+
// Expand embedding|... to array|items:number|...
|
|
49462
|
+
if (value.startsWith('embedding|') || value === 'embedding') {
|
|
49463
|
+
processed[key] = value.replace(/^embedding/, 'array|items:number|empty:false');
|
|
49464
|
+
continue;
|
|
49465
|
+
}
|
|
49466
|
+
processed[key] = value;
|
|
49467
|
+
} else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
49468
|
+
// Check if this is a validator type definition (has 'type' property that is NOT '$$type')
|
|
49469
|
+
// vs a nested object structure
|
|
49470
|
+
const hasValidatorType = value.type !== undefined && key !== '$$type';
|
|
49471
|
+
|
|
49472
|
+
if (hasValidatorType) {
|
|
49473
|
+
// This is a validator type definition (e.g., { type: 'array', items: 'number' }), pass it through
|
|
49474
|
+
processed[key] = value;
|
|
49475
|
+
} else {
|
|
49476
|
+
// This is a nested object structure, wrap it for validation
|
|
49477
|
+
const isExplicitRequired = value.$$type && value.$$type.includes('required');
|
|
49478
|
+
const isExplicitOptional = value.$$type && value.$$type.includes('optional');
|
|
49479
|
+
const objectConfig = {
|
|
49480
|
+
type: 'object',
|
|
49481
|
+
properties: this.preprocessAttributesForValidation(value),
|
|
49482
|
+
strict: false
|
|
49483
|
+
};
|
|
49484
|
+
// If explicitly required, don't mark as optional
|
|
49485
|
+
if (isExplicitRequired) ; else if (isExplicitOptional || this.allNestedObjectsOptional) {
|
|
49486
|
+
objectConfig.optional = true;
|
|
49487
|
+
}
|
|
49488
|
+
processed[key] = objectConfig;
|
|
49033
49489
|
}
|
|
49034
|
-
processed[key] = objectConfig;
|
|
49035
49490
|
} else {
|
|
49036
49491
|
processed[key] = value;
|
|
49037
49492
|
}
|
|
49038
49493
|
}
|
|
49039
|
-
|
|
49494
|
+
|
|
49040
49495
|
return processed;
|
|
49041
49496
|
}
|
|
49042
49497
|
}
|
|
@@ -49112,7 +49567,11 @@ class ResourceReader extends EventEmitter {
|
|
|
49112
49567
|
super();
|
|
49113
49568
|
|
|
49114
49569
|
if (!resource) {
|
|
49115
|
-
throw new
|
|
49570
|
+
throw new StreamError('Resource is required for ResourceReader', {
|
|
49571
|
+
operation: 'constructor',
|
|
49572
|
+
resource: resource?.name,
|
|
49573
|
+
suggestion: 'Pass a valid Resource instance when creating ResourceReader'
|
|
49574
|
+
});
|
|
49116
49575
|
}
|
|
49117
49576
|
|
|
49118
49577
|
this.resource = resource;
|
|
@@ -49269,7 +49728,10 @@ class ResourceWriter extends EventEmitter {
|
|
|
49269
49728
|
function streamToString(stream) {
|
|
49270
49729
|
return new Promise((resolve, reject) => {
|
|
49271
49730
|
if (!stream) {
|
|
49272
|
-
return reject(new
|
|
49731
|
+
return reject(new StreamError('Stream is undefined', {
|
|
49732
|
+
operation: 'streamToString',
|
|
49733
|
+
suggestion: 'Ensure a valid stream is passed to streamToString()'
|
|
49734
|
+
}));
|
|
49273
49735
|
}
|
|
49274
49736
|
const chunks = [];
|
|
49275
49737
|
stream.on('data', (chunk) => chunks.push(chunk));
|
|
@@ -49617,9 +50079,16 @@ async function handleInsert$4({ resource, data, mappedData, originalData }) {
|
|
|
49617
50079
|
});
|
|
49618
50080
|
|
|
49619
50081
|
if (totalSize > effectiveLimit) {
|
|
49620
|
-
throw new
|
|
50082
|
+
throw new MetadataLimitError('Metadata size exceeds 2KB limit on insert', {
|
|
50083
|
+
totalSize,
|
|
50084
|
+
effectiveLimit,
|
|
50085
|
+
absoluteLimit: S3_METADATA_LIMIT_BYTES,
|
|
50086
|
+
excess: totalSize - effectiveLimit,
|
|
50087
|
+
resourceName: resource.name,
|
|
50088
|
+
operation: 'insert'
|
|
50089
|
+
});
|
|
49621
50090
|
}
|
|
49622
|
-
|
|
50091
|
+
|
|
49623
50092
|
// If data fits in metadata, store only in metadata
|
|
49624
50093
|
return { mappedData, body: "" };
|
|
49625
50094
|
}
|
|
@@ -49638,7 +50107,15 @@ async function handleUpdate$4({ resource, id, data, mappedData, originalData })
|
|
|
49638
50107
|
});
|
|
49639
50108
|
|
|
49640
50109
|
if (totalSize > effectiveLimit) {
|
|
49641
|
-
throw new
|
|
50110
|
+
throw new MetadataLimitError('Metadata size exceeds 2KB limit on update', {
|
|
50111
|
+
totalSize,
|
|
50112
|
+
effectiveLimit,
|
|
50113
|
+
absoluteLimit: S3_METADATA_LIMIT_BYTES,
|
|
50114
|
+
excess: totalSize - effectiveLimit,
|
|
50115
|
+
resourceName: resource.name,
|
|
50116
|
+
operation: 'update',
|
|
50117
|
+
id
|
|
50118
|
+
});
|
|
49642
50119
|
}
|
|
49643
50120
|
return { mappedData, body: JSON.stringify(mappedData) };
|
|
49644
50121
|
}
|
|
@@ -49657,7 +50134,15 @@ async function handleUpsert$4({ resource, id, data, mappedData }) {
|
|
|
49657
50134
|
});
|
|
49658
50135
|
|
|
49659
50136
|
if (totalSize > effectiveLimit) {
|
|
49660
|
-
throw new
|
|
50137
|
+
throw new MetadataLimitError('Metadata size exceeds 2KB limit on upsert', {
|
|
50138
|
+
totalSize,
|
|
50139
|
+
effectiveLimit,
|
|
50140
|
+
absoluteLimit: S3_METADATA_LIMIT_BYTES,
|
|
50141
|
+
excess: totalSize - effectiveLimit,
|
|
50142
|
+
resourceName: resource.name,
|
|
50143
|
+
operation: 'upsert',
|
|
50144
|
+
id
|
|
50145
|
+
});
|
|
49661
50146
|
}
|
|
49662
50147
|
return { mappedData, body: "" };
|
|
49663
50148
|
}
|
|
@@ -50350,7 +50835,11 @@ const behaviors = {
|
|
|
50350
50835
|
function getBehavior(behaviorName) {
|
|
50351
50836
|
const behavior = behaviors[behaviorName];
|
|
50352
50837
|
if (!behavior) {
|
|
50353
|
-
throw new
|
|
50838
|
+
throw new BehaviorError(`Unknown behavior: ${behaviorName}`, {
|
|
50839
|
+
behavior: behaviorName,
|
|
50840
|
+
availableBehaviors: Object.keys(behaviors),
|
|
50841
|
+
operation: 'getBehavior'
|
|
50842
|
+
});
|
|
50354
50843
|
}
|
|
50355
50844
|
return behavior;
|
|
50356
50845
|
}
|
|
@@ -50478,6 +50967,7 @@ class Resource extends AsyncEventEmitter {
|
|
|
50478
50967
|
idGenerator: customIdGenerator,
|
|
50479
50968
|
idSize = 22,
|
|
50480
50969
|
versioningEnabled = false,
|
|
50970
|
+
strictValidation = true,
|
|
50481
50971
|
events = {},
|
|
50482
50972
|
asyncEvents = true,
|
|
50483
50973
|
asyncPartitions = true,
|
|
@@ -50493,6 +50983,7 @@ class Resource extends AsyncEventEmitter {
|
|
|
50493
50983
|
this.parallelism = parallelism;
|
|
50494
50984
|
this.passphrase = passphrase ?? 'secret';
|
|
50495
50985
|
this.versioningEnabled = versioningEnabled;
|
|
50986
|
+
this.strictValidation = strictValidation;
|
|
50496
50987
|
|
|
50497
50988
|
// Configure async events mode
|
|
50498
50989
|
this.setAsyncMode(asyncEvents);
|
|
@@ -50816,9 +51307,14 @@ class Resource extends AsyncEventEmitter {
|
|
|
50816
51307
|
|
|
50817
51308
|
/**
|
|
50818
51309
|
* Validate that all partition fields exist in current resource attributes
|
|
50819
|
-
* @throws {Error} If partition fields don't exist in current schema
|
|
51310
|
+
* @throws {Error} If partition fields don't exist in current schema (only when strictValidation is true)
|
|
50820
51311
|
*/
|
|
50821
51312
|
validatePartitions() {
|
|
51313
|
+
// Skip validation if strictValidation is disabled
|
|
51314
|
+
if (!this.strictValidation) {
|
|
51315
|
+
return;
|
|
51316
|
+
}
|
|
51317
|
+
|
|
50822
51318
|
if (!this.config.partitions) {
|
|
50823
51319
|
return; // No partitions to validate
|
|
50824
51320
|
}
|
|
@@ -53368,6 +53864,7 @@ class Database extends EventEmitter {
|
|
|
53368
53864
|
this.passphrase = options.passphrase || "secret";
|
|
53369
53865
|
this.versioningEnabled = options.versioningEnabled || false;
|
|
53370
53866
|
this.persistHooks = options.persistHooks || false; // New configuration for hook persistence
|
|
53867
|
+
this.strictValidation = options.strictValidation !== false; // Enable strict validation by default
|
|
53371
53868
|
|
|
53372
53869
|
// Initialize hooks system
|
|
53373
53870
|
this._initHooks();
|
|
@@ -53532,6 +54029,7 @@ class Database extends EventEmitter {
|
|
|
53532
54029
|
asyncEvents: versionData.asyncEvents !== undefined ? versionData.asyncEvents : true,
|
|
53533
54030
|
hooks: this.persistHooks ? this._deserializeHooks(versionData.hooks || {}) : (versionData.hooks || {}),
|
|
53534
54031
|
versioningEnabled: this.versioningEnabled,
|
|
54032
|
+
strictValidation: this.strictValidation,
|
|
53535
54033
|
map: versionData.map,
|
|
53536
54034
|
idGenerator: restoredIdGenerator,
|
|
53537
54035
|
idSize: restoredIdSize
|
|
@@ -53783,7 +54281,12 @@ class Database extends EventEmitter {
|
|
|
53783
54281
|
const plugin = this.plugins[pluginName] || this.pluginRegistry[pluginName];
|
|
53784
54282
|
|
|
53785
54283
|
if (!plugin) {
|
|
53786
|
-
throw new
|
|
54284
|
+
throw new DatabaseError(`Plugin '${name}' not found`, {
|
|
54285
|
+
operation: 'uninstallPlugin',
|
|
54286
|
+
pluginName: name,
|
|
54287
|
+
availablePlugins: Object.keys(this.pluginRegistry),
|
|
54288
|
+
suggestion: 'Check plugin name or list available plugins using Object.keys(db.pluginRegistry)'
|
|
54289
|
+
});
|
|
53787
54290
|
}
|
|
53788
54291
|
|
|
53789
54292
|
// Stop the plugin first
|
|
@@ -54332,6 +54835,7 @@ class Database extends EventEmitter {
|
|
|
54332
54835
|
autoDecrypt: config.autoDecrypt !== undefined ? config.autoDecrypt : true,
|
|
54333
54836
|
hooks: hooks || {},
|
|
54334
54837
|
versioningEnabled: this.versioningEnabled,
|
|
54838
|
+
strictValidation: this.strictValidation,
|
|
54335
54839
|
map: config.map,
|
|
54336
54840
|
idGenerator: config.idGenerator,
|
|
54337
54841
|
idSize: config.idSize,
|
|
@@ -54548,10 +55052,20 @@ class Database extends EventEmitter {
|
|
|
54548
55052
|
addHook(event, fn) {
|
|
54549
55053
|
if (!this._hooks) this._initHooks();
|
|
54550
55054
|
if (!this._hooks.has(event)) {
|
|
54551
|
-
throw new
|
|
55055
|
+
throw new DatabaseError(`Unknown hook event: ${event}`, {
|
|
55056
|
+
operation: 'addHook',
|
|
55057
|
+
invalidEvent: event,
|
|
55058
|
+
availableEvents: this._hookEvents,
|
|
55059
|
+
suggestion: `Use one of the available hook events: ${this._hookEvents.join(', ')}`
|
|
55060
|
+
});
|
|
54552
55061
|
}
|
|
54553
55062
|
if (typeof fn !== 'function') {
|
|
54554
|
-
throw new
|
|
55063
|
+
throw new DatabaseError('Hook function must be a function', {
|
|
55064
|
+
operation: 'addHook',
|
|
55065
|
+
event,
|
|
55066
|
+
receivedType: typeof fn,
|
|
55067
|
+
suggestion: 'Provide a function that will be called when the hook event occurs'
|
|
55068
|
+
});
|
|
54555
55069
|
}
|
|
54556
55070
|
this._hooks.get(event).push(fn);
|
|
54557
55071
|
}
|