@profullstack/storage-service
Version:
Flexible storage service abstraction for file storage, retrieval, and metadata management
250 lines (222 loc) • 7.2 kB
JavaScript
/**
* Metadata Utilities for Storage Service
*
* Provides functions for managing file metadata.
*/
/**
* Create a metadata manager
* @param {Object} options - Configuration options
* @param {string[]} options.reservedKeys - Reserved metadata keys that cannot be modified
* @param {Object} options.defaultMetadata - Default metadata to include with all files
* @param {Function} options.sanitizeKey - Function to sanitize metadata keys
* @param {Function} options.sanitizeValue - Function to sanitize metadata values
* @returns {Object} - Metadata manager
*/
export function createMetadataManager(options = {}) {
// Default options
const config = {
reservedKeys: ['contentType', 'size', 'createdAt', 'updatedAt'],
defaultMetadata: {},
sanitizeKey: key => key,
sanitizeValue: value => value,
...options
};
/**
* Prepare metadata for storage
* @param {Object} metadata - Metadata to prepare
* @param {Object} systemMetadata - System metadata to include
* @returns {Object} - Prepared metadata
*/
function prepareMetadata(metadata = {}, systemMetadata = {}) {
// Create a new metadata object
const preparedMetadata = {
// Include default metadata
...config.defaultMetadata,
// Include user metadata (sanitized)
...sanitizeMetadata(metadata),
// Include system metadata (overrides user metadata)
...systemMetadata
};
return preparedMetadata;
}
/**
* Sanitize metadata
* @param {Object} metadata - Metadata to sanitize
* @returns {Object} - Sanitized metadata
*/
function sanitizeMetadata(metadata = {}) {
const sanitizedMetadata = {};
// Process each metadata key-value pair
for (const [key, value] of Object.entries(metadata)) {
// Skip reserved keys
if (config.reservedKeys.includes(key)) {
continue;
}
// Sanitize key and value
const sanitizedKey = config.sanitizeKey(key);
const sanitizedValue = config.sanitizeValue(value);
// Add to sanitized metadata
sanitizedMetadata[sanitizedKey] = sanitizedValue;
}
return sanitizedMetadata;
}
/**
* Filter metadata for public access
* @param {Object} metadata - Metadata to filter
* @param {string[]} publicKeys - Keys to include in public metadata
* @returns {Object} - Filtered metadata
*/
function filterPublicMetadata(metadata = {}, publicKeys = []) {
// If no public keys specified, return empty object
if (!publicKeys.length) {
return {};
}
const publicMetadata = {};
// Include only specified public keys
for (const key of publicKeys) {
if (metadata.hasOwnProperty(key)) {
publicMetadata[key] = metadata[key];
}
}
return publicMetadata;
}
/**
* Extract system metadata
* @param {Object} metadata - Metadata to extract from
* @returns {Object} - System metadata
*/
function extractSystemMetadata(metadata = {}) {
const systemMetadata = {};
// Extract reserved keys
for (const key of config.reservedKeys) {
if (metadata.hasOwnProperty(key)) {
systemMetadata[key] = metadata[key];
}
}
return systemMetadata;
}
/**
* Extract user metadata
* @param {Object} metadata - Metadata to extract from
* @returns {Object} - User metadata
*/
function extractUserMetadata(metadata = {}) {
const userMetadata = {};
// Extract non-reserved keys
for (const [key, value] of Object.entries(metadata)) {
if (!config.reservedKeys.includes(key)) {
userMetadata[key] = value;
}
}
return userMetadata;
}
/**
* Validate metadata
* @param {Object} metadata - Metadata to validate
* @param {Object} schema - Validation schema
* @returns {Object} - Validation result
* @returns {boolean} result.valid - Whether the metadata is valid
* @returns {string} result.error - Error message if invalid
*/
function validateMetadata(metadata = {}, schema = {}) {
// Check required fields
if (schema.required) {
for (const key of schema.required) {
if (!metadata.hasOwnProperty(key)) {
return {
valid: false,
error: `Missing required metadata field: ${key}`
};
}
}
}
// Check field types
if (schema.types) {
for (const [key, type] of Object.entries(schema.types)) {
if (metadata.hasOwnProperty(key)) {
const value = metadata[key];
// Check type
if (type === 'string' && typeof value !== 'string') {
return {
valid: false,
error: `Metadata field ${key} must be a string`
};
} else if (type === 'number' && typeof value !== 'number') {
return {
valid: false,
error: `Metadata field ${key} must be a number`
};
} else if (type === 'boolean' && typeof value !== 'boolean') {
return {
valid: false,
error: `Metadata field ${key} must be a boolean`
};
} else if (type === 'object' && (typeof value !== 'object' || value === null || Array.isArray(value))) {
return {
valid: false,
error: `Metadata field ${key} must be an object`
};
} else if (type === 'array' && !Array.isArray(value)) {
return {
valid: false,
error: `Metadata field ${key} must be an array`
};
}
}
}
}
// Check field patterns
if (schema.patterns) {
for (const [key, pattern] of Object.entries(schema.patterns)) {
if (metadata.hasOwnProperty(key)) {
const value = metadata[key];
// Check pattern
if (typeof value === 'string' && !pattern.test(value)) {
return {
valid: false,
error: `Metadata field ${key} does not match required pattern`
};
}
}
}
}
// Check field validators
if (schema.validators) {
for (const [key, validator] of Object.entries(schema.validators)) {
if (metadata.hasOwnProperty(key)) {
const value = metadata[key];
// Run validator
try {
const result = validator(value);
if (result !== true) {
return {
valid: false,
error: result || `Metadata field ${key} failed validation`
};
}
} catch (error) {
return {
valid: false,
error: error.message || `Metadata field ${key} failed validation`
};
}
}
}
}
// Metadata is valid
return {
valid: true,
error: null
};
}
// Return the metadata manager
return {
prepareMetadata,
sanitizeMetadata,
filterPublicMetadata,
extractSystemMetadata,
extractUserMetadata,
validateMetadata
};
}
export default createMetadataManager;