s3db.js
Version:
Use AWS S3, the world's most reliable document storage, as a database with this ORM.
168 lines (154 loc) • 6.07 kB
JavaScript
import { calculateTotalSize } from '../concerns/calculator.js';
import { calculateEffectiveLimit } from '../concerns/calculator.js';
import { S3_METADATA_LIMIT_BYTES } from './enforce-limits.js';
/**
* User Managed Behavior Configuration Documentation
*
* The `user-managed` behavior is the default for s3db resources. It provides no automatic enforcement
* of S3 metadata or body size limits, and does not modify or truncate data. Instead, it emits warnings
* via the `exceedsLimit` event when S3 metadata limits are exceeded, but allows all operations to proceed.
*
* ## Purpose & Use Cases
* - For development, testing, or advanced users who want full control over resource metadata and body size.
* - Useful when you want to handle S3 metadata limits yourself, or implement custom logic for warnings.
* - Not recommended for production unless you have custom enforcement or validation in place.
*
* ## How It Works
* - Emits an `exceedsLimit` event (with details) when a resource's metadata size exceeds the S3 2KB limit.
* - Does NOT block, truncate, or modify data—operations always proceed.
* - No automatic enforcement of any limits; user is responsible for handling warnings and data integrity.
*
* ## Event Emission
* - Event: `exceedsLimit`
* - Payload:
* - `operation`: 'insert' | 'update' | 'upsert'
* - `id` (for update/upsert): resource id
* - `totalSize`: total metadata size in bytes
* - `limit`: S3 metadata limit (2048 bytes)
* - `excess`: number of bytes over the limit
* - `data`: the offending data object
*
* @example
* // Listen for warnings on a resource
* resource.on('exceedsLimit', (info) => {
* console.warn(`Resource exceeded S3 metadata limit:`, info);
* });
*
* @example
* // Create a resource with user-managed behavior (default)
* const resource = await db.createResource({
* name: 'my_resource',
* attributes: { ... },
* behavior: 'user-managed' // or omit for default
* });
*
* ## Comparison to Other Behaviors
* | Behavior | Enforcement | Data Loss | Event Emission | Use Case |
* |------------------|-------------|-----------|----------------|-------------------------|
* | user-managed | None | Possible | Warns | Dev/Test/Advanced users |
* | enforce-limits | Strict | No | Throws | Production |
* | truncate-data | Truncates | Yes | Warns | Content Mgmt |
* | body-overflow | Truncates/Splits | Yes | Warns | Large objects |
*
* ## Best Practices & Warnings
* - Exceeding S3 metadata limits will cause silent data loss or errors at the storage layer.
* - Use this behavior only if you have custom logic to handle warnings and enforce limits.
* - For production, prefer `enforce-limits` or `truncate-data` to avoid data loss.
*
* ## Migration Tips
* - To migrate to a stricter behavior, change the resource's behavior to `enforce-limits` or `truncate-data`.
* - Review emitted warnings to identify resources at risk of exceeding S3 limits.
*
* @typedef {Object} UserManagedBehaviorConfig
* @property {boolean} [enabled=true] - Whether the behavior is active
*/
export async function handleInsert({ resource, data, mappedData, originalData }) {
const totalSize = calculateTotalSize(mappedData);
// Calculate effective limit considering system overhead
const effectiveLimit = calculateEffectiveLimit({
s3Limit: S3_METADATA_LIMIT_BYTES,
systemConfig: {
version: resource.version,
timestamps: resource.config.timestamps,
id: data.id
}
});
if (totalSize > effectiveLimit) {
resource.emit('exceedsLimit', {
operation: 'insert',
totalSize,
limit: 2047,
excess: totalSize - 2047,
data: originalData || data
});
// If data exceeds limit, store in body
return { mappedData: { _v: mappedData._v }, body: JSON.stringify(mappedData) };
}
// If data fits in metadata, store only in metadata
return { mappedData, body: "" };
}
export async function handleUpdate({ resource, id, data, mappedData, originalData }) {
const totalSize = calculateTotalSize(mappedData);
// Calculate effective limit considering system overhead
const effectiveLimit = calculateEffectiveLimit({
s3Limit: S3_METADATA_LIMIT_BYTES,
systemConfig: {
version: resource.version,
timestamps: resource.config.timestamps,
id
}
});
if (totalSize > effectiveLimit) {
resource.emit('exceedsLimit', {
operation: 'update',
id,
totalSize,
limit: 2047,
excess: totalSize - 2047,
data: originalData || data
});
}
return { mappedData, body: JSON.stringify(data) };
}
export async function handleUpsert({ resource, id, data, mappedData, originalData }) {
const totalSize = calculateTotalSize(mappedData);
// Calculate effective limit considering system overhead
const effectiveLimit = calculateEffectiveLimit({
s3Limit: S3_METADATA_LIMIT_BYTES,
systemConfig: {
version: resource.version,
timestamps: resource.config.timestamps,
id
}
});
if (totalSize > effectiveLimit) {
resource.emit('exceedsLimit', {
operation: 'upsert',
id,
totalSize,
limit: 2047,
excess: totalSize - 2047,
data: originalData || data
});
}
return { mappedData, body: JSON.stringify(data) };
}
export async function handleGet({ resource, metadata, body }) {
// If body contains data, parse it and merge with metadata
if (body && body.trim() !== '') {
try {
const bodyData = JSON.parse(body);
// Merge body data with metadata, with metadata taking precedence
const mergedData = {
...bodyData,
...metadata
};
return { metadata: mergedData, body };
} catch (error) {
// If parsing fails, return original metadata and body
return { metadata, body };
}
}
// If no body data, return metadata as is
return { metadata, body };
}