fortify2-js
Version:
MOST POWERFUL JavaScript Security Library! Military-grade cryptography + 19 enhanced object methods + quantum-resistant algorithms + perfect TypeScript support. More powerful than Lodash with built-in security.
1,374 lines (1,367 loc) • 58.2 kB
JavaScript
'use strict';
var secureMemory = require('../../secure-memory.js');
var index = require('../../../utils/memory/index.js');
var sensitiveKeys = require('../encryption/sensitive-keys.js');
var cryptoHandler = require('../encryption/crypto-handler.js');
var metadataManager = require('../metadata/metadata-manager.js');
var eventManager = require('../events/event-manager.js');
var serializationHandler = require('../serialization/serialization-handler.js');
var idGenerator = require('../utils/id-generator.js');
var validation = require('../utils/validation.js');
var secureStringCore = require('../../secure-string/core/secure-string-core.js');
require('crypto');
require('../../secure-string/advanced/entropy-analyzer.js');
require('../../secure-string/advanced/quantum-safe.js');
require('../../secure-string/advanced/performance-monitor.js');
var types = require('../../../utils/memory/types.js');
/***************************************************************************
* FortifyJS - Secure Array Types
*
* This file contains type definitions for the SecureArray modular architecture
*
* @author Nehonix
*
* @license MIT
*
* Copyright (c) 2025 Nehonix. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
***************************************************************************** */
/**
* A secure object that can store sensitive data
* T represents the initial type, but the object can be extended with additional keys
*/
class SecureObject {
/**
* Creates a new secure object
*/
constructor(initialData, options) {
// Core data storage
this.data = new Map();
this.secureBuffers = new Map();
// State management
this._isDestroyed = false;
this._isReadOnly = false;
// Enhanced memory management
this._memoryTracking = false;
this._autoCleanup = false;
this._createdAt = Date.now();
this._lastAccessed = Date.now();
this._id = idGenerator.IdGenerator.generate();
this._isReadOnly = false; // Start as writable
// Initialize modular components
this.sensitiveKeysManager = new sensitiveKeys.SensitiveKeysManager();
this.cryptoHandler = new cryptoHandler.CryptoHandler(this._id);
this.metadataManager = new metadataManager.MetadataManager();
this.eventManager = new eventManager.EventManager();
this.serializationHandler = new serializationHandler.SerializationHandler(this.cryptoHandler, this.metadataManager);
// Set encryption key if provided
if (options?.encryptionKey) {
this.cryptoHandler.setEncryptionKey(options.encryptionKey);
}
// Configure memory management with enhanced features
this._memoryTracking = options?.enableMemoryTracking ?? true; // Enable by default
this._autoCleanup = options?.autoCleanup ?? true; // Enable by default
// Set memory limits if provided
if (options?.maxMemory) {
index.memoryManager.setLimits(options.maxMemory, options.gcThreshold || 0.8);
}
// Register with advanced memory manager if tracking is enabled
if (this._memoryTracking) {
index.memoryManager.registerObject(this, this._id);
// Listen to memory events for proactive management
index.memoryManager.on(types.MemoryEventType.MEMORY_PRESSURE, (event) => {
if (event.data?.pressure > 0.8) {
this.handleMemoryPressure();
}
});
index.memoryManager.on(types.MemoryEventType.LEAK_DETECTED, (event) => {
if (event.data?.leaks?.includes(this._id)) {
console.warn(`Potential memory leak detected in SecureObject ${this._id}`);
}
});
}
// Create memory pool for secure buffers if not exists
this.initializeSecureBufferPool();
// Set initial data
if (initialData) {
this.setAll(initialData);
}
// Set read-only status after initial data is set
this._isReadOnly = options?.readOnly ?? false;
}
/**
* Creates a SecureObject from another SecureObject (deep copy)
*/
static from(other) {
other.ensureNotDestroyed();
const copy = new SecureObject();
for (const key of other.keys()) {
const value = other.get(key);
copy.set(String(key), value);
}
return copy;
}
/**
* Creates a read-only SecureObject
*/
static readOnly(data) {
return new SecureObject(data, { readOnly: true });
}
/**
* Creates a read-only SecureObject (public usage)
*/
/** Permanently enable read-only mode (cannot be disabled). */
enableReadOnly() {
this.ensureNotDestroyed();
this._isReadOnly = true;
return this;
}
// ===== PROPERTY ACCESSORS =====
/**
* Gets the unique ID of this SecureObject
*/
get id() {
return this._id;
}
/**
* Checks if the SecureObject is read-only
*/
get isReadOnly() {
return this._isReadOnly;
}
/**
* Checks if the SecureObject has been destroyed
*/
get isDestroyed() {
return this._isDestroyed;
}
/**
* Gets the number of stored values
*/
get size() {
this.ensureNotDestroyed();
return this.data.size;
}
/**
* Checks if the object is empty
*/
get isEmpty() {
this.ensureNotDestroyed();
return this.data.size === 0;
}
// ===== VALIDATION METHODS =====
/**
* Ensures the SecureObject hasn't been destroyed
*/
ensureNotDestroyed() {
if (this._isDestroyed) {
throw new Error("SecureObject has been destroyed and cannot be used");
}
}
/**
* Ensures the SecureObject is not read-only for write operations
*/
ensureNotReadOnly() {
if (this._isReadOnly) {
throw new Error("SecureObject is read-only");
}
}
/**
* Updates the last accessed timestamp for memory management
*/
updateLastAccessed() {
this._lastAccessed = Date.now();
}
// ===== MEMORY MANAGEMENT =====
/**
* Initialize secure buffer pool for efficient memory reuse
*/
initializeSecureBufferPool() {
if (!this.secureBufferPool) {
try {
this.secureBufferPool =
index.memoryManager.getPool("secure-buffer-pool") ||
index.memoryManager.createPool({
name: "secure-buffer-pool",
factory: () => new Uint8Array(1024), // 1KB buffers
reset: (buffer) => {
// Secure wipe before reuse
this.secureWipe(buffer);
},
capacity: 50,
strategy: types.PoolStrategy.LRU,
validator: (buffer) => buffer instanceof Uint8Array,
});
}
catch (error) {
// Pool might already exist, try to get it
this.secureBufferPool =
index.memoryManager.getPool("secure-buffer-pool");
}
}
}
/**
* Handle memory pressure situations
*/
handleMemoryPressure() {
if (this._autoCleanup) {
// Clean up unused secure buffers
this.forceGarbageCollection();
// Emit event for external handlers
this.eventManager.emit("gc", undefined, {
timestamp: Date.now(),
objectId: this._id,
action: "memory_pressure_cleanup",
});
}
}
/**
* Secure wipe of buffer memory
*/
secureWipe(buffer) {
if (!buffer || buffer.length === 0)
return;
// Multiple-pass secure wipe
const passes = [0x00, 0xff, 0xaa, 0x55, 0x00];
for (const pattern of passes) {
buffer.fill(pattern);
}
// Final random pass if crypto is available
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
crypto.getRandomValues(buffer);
}
buffer.fill(0x00); // Final zero pass
}
/**
* Gets enhanced memory usage statistics for this SecureObject
*/
getMemoryUsage() {
this.ensureNotDestroyed();
let allocatedMemory = 0;
for (const buffer of this.secureBuffers.values()) {
allocatedMemory += buffer.length();
}
const now = Date.now();
const usage = {
allocatedMemory,
bufferCount: this.secureBuffers.size,
dataSize: this.data.size,
createdAt: this._createdAt,
lastAccessed: this._lastAccessed,
age: now - this._createdAt,
formattedMemory: index.MemoryUtils.formatBytes(allocatedMemory),
poolStats: this.secureBufferPool?.getStats(),
};
return usage;
}
/**
* Forces enhanced garbage collection for this SecureObject
*/
forceGarbageCollection() {
this.ensureNotDestroyed();
if (this._memoryTracking) {
const beforeUsage = this.getMemoryUsage();
// Clean up unused secure buffers with secure wipe
for (const [key, buffer] of this.secureBuffers.entries()) {
if (!this.data.has(key)) {
// Secure wipe before destroying (get buffer data safely)
try {
const bufferData = buffer.getBuffer(); // Use correct method
if (bufferData instanceof Uint8Array) {
this.secureWipe(bufferData);
}
}
catch (error) {
// Buffer might already be destroyed, continue
}
buffer.destroy();
this.secureBuffers.delete(key);
}
}
// Return unused buffers to pool
if (this.secureBufferPool) ;
// Trigger global GC with enhanced features
const gcResult = index.memoryManager.forceGC();
const afterUsage = this.getMemoryUsage();
const freedMemory = beforeUsage.allocatedMemory - afterUsage.allocatedMemory;
this.eventManager.emit("gc", undefined, {
timestamp: Date.now(),
bufferCount: this.secureBuffers.size,
freedMemory,
gcDuration: gcResult.duration,
gcSuccess: gcResult.success,
beforeUsage: beforeUsage.formattedMemory,
afterUsage: afterUsage.formattedMemory,
});
}
}
/**
* Enables memory tracking for this SecureObject
*/
enableMemoryTracking() {
this.ensureNotDestroyed();
if (!this._memoryTracking) {
this._memoryTracking = true;
index.memoryManager.registerObject(this, this._id);
}
return this;
}
/**
* Disables memory tracking for this SecureObject
*/
disableMemoryTracking() {
this.ensureNotDestroyed();
if (this._memoryTracking) {
this._memoryTracking = false;
index.memoryManager.removeReference(this._id);
}
return this;
}
// ===== SENSITIVE KEYS MANAGEMENT =====
/**
* Adds keys to the sensitive keys list
*/
addSensitiveKeys(...keys) {
this.ensureNotDestroyed();
validation.ValidationUtils.validateKeys(keys);
this.sensitiveKeysManager.add(...keys);
return this;
}
/**
* Removes keys from the sensitive keys list
*/
removeSensitiveKeys(...keys) {
this.ensureNotDestroyed();
validation.ValidationUtils.validateKeys(keys);
this.sensitiveKeysManager.remove(...keys);
return this;
}
/**
* Sets the complete list of sensitive keys (replaces existing list)
*/
setSensitiveKeys(keys) {
this.ensureNotDestroyed();
validation.ValidationUtils.validateKeys(keys);
this.sensitiveKeysManager.set(keys);
return this;
}
/**
* Gets the current list of sensitive keys
*/
getSensitiveKeys() {
this.ensureNotDestroyed();
return this.sensitiveKeysManager.getAll();
}
/**
* Checks if a key is marked as sensitive
*/
isSensitiveKey(key) {
validation.ValidationUtils.validateKey(key);
return this.sensitiveKeysManager.isSensitive(key);
}
/**
* Clears all sensitive keys
*/
clearSensitiveKeys() {
this.ensureNotDestroyed();
this.sensitiveKeysManager.clear();
return this;
}
/**
* Resets sensitive keys to default values
*/
resetToDefaultSensitiveKeys() {
this.ensureNotDestroyed();
this.sensitiveKeysManager.resetToDefault();
return this;
}
/**
* Gets the default sensitive keys that are automatically initialized
*/
static get getDefaultSensitiveKeys() {
return sensitiveKeys.SensitiveKeysManager.getDefaultKeys();
}
/**
* Adds custom regex patterns for sensitive key detection
* @param patterns - Regex patterns or strings to match sensitive keys
*/
addSensitivePatterns(...patterns) {
this.ensureNotDestroyed();
this.sensitiveKeysManager.addCustomPatterns(...patterns);
return this;
}
/**
* Removes custom sensitive patterns
*/
removeSensitivePatterns(...patterns) {
this.ensureNotDestroyed();
this.sensitiveKeysManager.removeCustomPatterns(...patterns);
return this;
}
/**
* Clears all custom sensitive patterns
*/
clearSensitivePatterns() {
this.ensureNotDestroyed();
this.sensitiveKeysManager.clearCustomPatterns();
return this;
}
/**
* Gets all custom sensitive patterns
*/
getSensitivePatterns() {
this.ensureNotDestroyed();
return this.sensitiveKeysManager.getCustomPatterns();
}
// ===== ENCRYPTION MANAGEMENT =====
/**
* Sets the encryption key for sensitive data encryption
*/
setEncryptionKey(key = null) {
this.ensureNotDestroyed();
validation.ValidationUtils.validateEncryptionKey(key);
this.cryptoHandler.setEncryptionKey(key);
return this;
}
/**
* Gets the current encryption key
*/
get getEncryptionKey() {
return this.cryptoHandler.getEncryptionKey();
}
/**
* Decrypts a value using the encryption key
*/
decryptValue(encryptedValue) {
this.ensureNotDestroyed();
return this.cryptoHandler.decryptValue(encryptedValue);
}
/**
* Decrypts all encrypted values in an object
*/
decryptObject(obj) {
this.ensureNotDestroyed();
return this.cryptoHandler.decryptObject(obj);
}
/**
* Encrypts all values in the object using AES-256-CTR-HMAC encryption
* with proper memory management and atomic operations
*/
encryptAll() {
this.ensureNotDestroyed();
this.ensureNotReadOnly();
// Check if encryption key is set
const encryptionStatus = this.cryptoHandler.getEncryptionStatus();
if (!encryptionStatus.hasEncryptionKey) {
throw new Error("Encryption key must be set before calling encryptAll()");
}
// Prepare temporary map for atomic operation
const encryptedEntries = new Map();
const originalMetadata = new Map();
const keysToProcess = [];
try {
// First pass: encrypt all values into temporary storage
for (const [key, value] of this.data.entries()) {
if (value !== undefined) {
// Skip already encrypted values to avoid double encryption
if (typeof value === "string" &&
this.cryptoHandler.isEncrypted(value)) {
continue;
}
// Store original metadata for rollback
if (this.metadataManager.has(key)) {
originalMetadata.set(key, this.metadataManager.get(key));
}
// Get the actual value to encrypt
let valueToEncrypt = value;
// If it's a SecureBuffer, convert it back to its original form
if (value instanceof secureMemory.SecureBuffer) {
const metadata = this.metadataManager.get(key);
if (metadata?.type === "string") {
valueToEncrypt = new TextDecoder().decode(value.getBuffer());
}
else if (metadata?.type === "Uint8Array") {
valueToEncrypt = new Uint8Array(value.getBuffer());
}
else {
valueToEncrypt = new TextDecoder().decode(value.getBuffer());
}
}
// Encrypt the value
const encryptedValue = this.cryptoHandler.encryptValue(valueToEncrypt);
encryptedEntries.set(key, encryptedValue);
keysToProcess.push(key);
}
}
// Second pass: atomically commit all changes
for (const key of keysToProcess) {
const encryptedValue = encryptedEntries.get(key);
const originalValue = this.data.get(key);
// Clean up any existing SecureBuffer for this key
this.cleanupKey(key);
// Store encrypted value
this.data.set(key, encryptedValue);
// Update metadata with correct type information
// Store original type in the type field using special format
const originalType = typeof originalValue;
this.metadataManager.update(key, `encrypted:${originalType}`, true);
}
}
catch (error) {
// Rollback: restore original state on any failure
for (const key of keysToProcess) {
if (originalMetadata.has(key)) {
// Restore original metadata
const original = originalMetadata.get(key);
this.metadataManager.update(key, original.type, original.isSecure);
}
}
throw new Error(`Encryption failed: ${error.message}`);
}
this.updateLastAccessed();
this.eventManager.emit("set", "encrypt_all", `${keysToProcess.length}_values_encrypted`);
return this;
}
/**
* Gets the raw encrypted data without decryption (for verification)
*/
getRawEncryptedData() {
this.ensureNotDestroyed();
return new Map(this.data);
}
/**
* Gets a specific key's raw encrypted form (for verification)
*/
getRawEncryptedValue(key) {
this.ensureNotDestroyed();
const stringKey = validation.ValidationUtils.sanitizeKey(key);
return this.data.get(stringKey);
}
/**
* Gets encryption status from the crypto handler
*/
getEncryptionStatus() {
return this.cryptoHandler.getEncryptionStatus();
}
// ===== EVENT MANAGEMENT =====
/**
* Adds an event listener
*/
addEventListener(event, listener) {
validation.ValidationUtils.validateEventType(event);
validation.ValidationUtils.validateEventListener(listener);
this.eventManager.addEventListener(event, listener);
}
/**
* Removes an event listener
*/
removeEventListener(event, listener) {
validation.ValidationUtils.validateEventType(event);
validation.ValidationUtils.validateEventListener(listener);
this.eventManager.removeEventListener(event, listener);
}
/**
* Creates a one-time event listener
*/
once(event, listener) {
validation.ValidationUtils.validateEventType(event);
validation.ValidationUtils.validateEventListener(listener);
this.eventManager.once(event, listener);
}
/**
* Waits for a specific event to be emitted
*/
waitFor(event, timeout) {
validation.ValidationUtils.validateEventType(event);
if (timeout !== undefined) {
validation.ValidationUtils.validateTimeout(timeout);
}
return this.eventManager.waitFor(event, timeout);
}
// ===== CORE DATA OPERATIONS =====
/**
* Sets a value - allows both existing keys and new dynamic keys
*/
set(key, value) {
this.ensureNotDestroyed();
this.ensureNotReadOnly();
this.updateLastAccessed();
const stringKey = validation.ValidationUtils.sanitizeKey(key);
validation.ValidationUtils.isValidSecureValue(value);
// Clean up any existing secure buffer for this key
this.cleanupKey(stringKey);
// Handle different types of values
if (value &&
typeof value === "object" &&
value.constructor.name === "Uint8Array") {
// Store Uint8Array in a secure buffer
const secureBuffer = secureMemory.SecureBuffer.from(value);
this.secureBuffers.set(stringKey, secureBuffer);
this.data.set(stringKey, secureBuffer);
this.metadataManager.update(stringKey, "Uint8Array", true);
}
else if (typeof value === "string") {
// Store strings in secure buffers
const secureBuffer = secureMemory.SecureBuffer.from(value);
this.secureBuffers.set(stringKey, secureBuffer);
this.data.set(stringKey, secureBuffer);
this.metadataManager.update(stringKey, "string", true);
}
else if (validation.ValidationUtils.isSecureString(value)) {
// Store SecureString reference
this.data.set(stringKey, value);
this.metadataManager.update(stringKey, "SecureString", true);
}
else if (validation.ValidationUtils.isSecureObject(value)) {
// Store SecureObject reference
this.data.set(stringKey, value);
this.metadataManager.update(stringKey, "SecureObject", true);
}
else {
// Store other values directly (numbers, booleans, null, undefined)
this.data.set(stringKey, value);
this.metadataManager.update(stringKey, typeof value, false);
}
this.eventManager.emit("set", stringKey, value);
return this;
}
/**
* Sets multiple values at once
*/
setAll(values) {
this.ensureNotDestroyed();
this.ensureNotReadOnly();
for (const key in values) {
if (Object.prototype.hasOwnProperty.call(values, key)) {
this.set(String(key), values[key]);
}
}
return this;
}
/**
* Gets a value with automatic decryption
*/
get(key) {
this.ensureNotDestroyed();
this.updateLastAccessed();
const stringKey = validation.ValidationUtils.sanitizeKey(key);
const value = this.data.get(stringKey);
// Update access metadata
if (this.metadataManager.has(stringKey)) {
const metadata = this.metadataManager.get(stringKey);
this.metadataManager.update(stringKey, metadata.type, metadata.isSecure);
}
// Check if value is encrypted (starts with [ENCRYPTED:)
if (typeof value === "string" &&
this.cryptoHandler.isEncrypted(value)) {
try {
// Decrypt the value automatically
const decryptedValue = this.cryptoHandler.decryptValue(value);
this.eventManager.emit("get", stringKey, decryptedValue);
return decryptedValue;
}
catch (error) {
console.error(`Failed to decrypt value for key ${stringKey}:`, error);
// Return the encrypted value if decryption fails
this.eventManager.emit("get", stringKey, value);
return value;
}
}
if (value instanceof secureMemory.SecureBuffer) {
// Convert SecureBuffer back to original type based on metadata
const buffer = value.getBuffer();
const metadata = this.metadataManager.get(stringKey);
if (metadata?.type === "Uint8Array" ||
metadata?.type === "encrypted:Uint8Array") {
// Return as Uint8Array for binary data
const result = new Uint8Array(buffer);
this.eventManager.emit("get", stringKey, result);
return result;
}
else {
// Return as string for text data
const result = new TextDecoder().decode(buffer);
this.eventManager.emit("get", stringKey, result);
return result;
}
}
this.eventManager.emit("get", stringKey, value);
return value;
}
/**
* Gets a value safely, returning undefined if key doesn't exist
*/
getSafe(key) {
try {
return this.has(key) ? this.get(key) : undefined;
}
catch {
return undefined;
}
}
/**
* Gets a value with a default fallback
*/
getWithDefault(key, defaultValue) {
return this.has(key) ? this.get(key) : defaultValue;
}
/**
* Checks if a key exists
*/
has(key) {
this.ensureNotDestroyed();
const stringKey = validation.ValidationUtils.sanitizeKey(key);
return this.data.has(stringKey);
}
/**
* Deletes a key - allows both existing keys and dynamic keys
*/
delete(key) {
this.ensureNotDestroyed();
this.ensureNotReadOnly();
const stringKey = validation.ValidationUtils.sanitizeKey(key);
if (!this.data.has(stringKey)) {
return false;
}
// Clean up any secure buffer
this.cleanupKey(stringKey);
const deleted = this.data.delete(stringKey);
this.metadataManager.delete(stringKey);
this.eventManager.emit("delete", stringKey);
return deleted;
}
/**
* Cleans up resources associated with a key
*/
cleanupKey(key) {
if (this.secureBuffers.has(key)) {
this.secureBuffers.get(key)?.destroy();
this.secureBuffers.delete(key);
}
// Note: We don't destroy SecureString or SecureObject instances
// as they might be used elsewhere
}
/**
* Clears all data
*/
clear() {
this.ensureNotDestroyed();
this.ensureNotReadOnly();
// Destroy all secure buffers
for (const buffer of this.secureBuffers.values()) {
buffer.destroy();
}
this.secureBuffers.clear();
this.data.clear();
this.metadataManager.clear();
this.eventManager.emit("clear");
}
// ===== ITERATION AND COLLECTION METHODS =====
/**
* Gets all keys
*/
keys() {
this.ensureNotDestroyed();
return Array.from(this.data.keys());
}
/**
* Gets all values
*/
values() {
this.ensureNotDestroyed();
return this.keys().map((key) => this.get(key));
}
/**
* Gets all entries as [key, value] pairs
*/
entries() {
this.ensureNotDestroyed();
return this.keys().map((key) => [key, this.get(key)]);
}
/**
* Iterates over each key-value pair
*/
forEach(callback) {
this.ensureNotDestroyed();
validation.ValidationUtils.validateCallback(callback, "forEach callback");
for (const key of this.keys()) {
callback(this.get(key), key, this);
}
}
/**
* Maps over values and returns a new array
*/
map(callback) {
this.ensureNotDestroyed();
validation.ValidationUtils.validateMapper(callback);
return this.keys().map((key) => callback(this.get(key), key, this));
}
/**
* Filters entries based on a predicate function (like Array.filter)
* Returns a new SecureObject with only the entries that match the condition
*/
filter(predicate) {
this.ensureNotDestroyed();
validation.ValidationUtils.validatePredicate(predicate);
const filtered = new SecureObject();
for (const key of this.keys()) {
const value = this.get(key);
if (predicate(value, key, this)) {
filtered.set(String(key), value);
}
}
// Emit event with filter details
this.eventManager.emit("filtered", undefined, {
operation: "filter",
resultSize: filtered.size,
originalSize: this.size,
});
return filtered;
}
/**
* Filters entries by specific key names (type-safe for known keys)
* Returns a new SecureObject with only the specified keys
*
* @example
* const user = createSecureObject({ name: "John", password: "secret", age: 30 });
* const credentials = user.filterByKeys("name", "password");
*/
filterByKeys(...keys) {
this.ensureNotDestroyed();
const filtered = new SecureObject();
for (const key of keys) {
if (this.has(key)) {
const stringKey = String(key);
filtered.set(stringKey, this.get(key));
}
}
// Copy sensitive keys that are included in the filter
const relevantSensitiveKeys = this.getSensitiveKeys().filter((k) => keys.includes(k));
if (relevantSensitiveKeys.length > 0) {
filtered.setSensitiveKeys(relevantSensitiveKeys);
}
this.eventManager.emit("filtered", undefined, {
operation: "filterByKeys",
keys: keys.map((k) => String(k)),
resultSize: filtered.size,
});
return filtered;
}
/**
* Filters entries by value type using a type guard function
* Returns a new SecureObject with only values of the specified type
*
* @example
* const data = createSecureObject({ name: "John", age: 30, active: true });
* const strings = data.filterByType((v): v is string => typeof v === "string");
*/
filterByType(typeGuard) {
this.ensureNotDestroyed();
const filtered = new SecureObject();
for (const key of this.keys()) {
const value = this.get(key);
if (typeGuard(value)) {
const stringKey = String(key);
filtered.set(stringKey, value);
}
}
this.eventManager.emit("filtered", undefined, {
operation: "filterByType",
resultSize: filtered.size,
});
return filtered;
}
/**
* Filters entries to only include sensitive keys
* Returns a new SecureObject with only sensitive data
*
* @example
* const user = createSecureObject({ name: "John", password: "secret", age: 30 });
* user.addSensitiveKeys("password");
* const sensitiveData = user.filterSensitive(); // Only contains password
*/
filterSensitive() {
this.ensureNotDestroyed();
const filtered = new SecureObject();
const sensitiveKeys = this.getSensitiveKeys();
for (const key of this.keys()) {
const stringKey = String(key);
if (sensitiveKeys.includes(stringKey)) {
filtered.set(stringKey, this.get(key));
}
}
// Copy all sensitive keys to the filtered object
filtered.setSensitiveKeys([...sensitiveKeys]);
this.eventManager.emit("filtered", undefined, {
operation: "filterSensitive",
resultSize: filtered.size,
});
return filtered;
}
/**
* Filters entries to exclude sensitive keys
* Returns a new SecureObject with only non-sensitive data
*
* @example
* const user = createSecureObject({ name: "John", password: "secret", age: 30 });
* user.addSensitiveKeys("password");
* const publicData = user.filterNonSensitive(); // Contains name and age
*/
filterNonSensitive(options) {
this.ensureNotDestroyed();
// Default to non-strict mode (false) - only exact matches
const strictMode = options?.strictMode === true;
const filtered = new SecureObject();
for (const key of this.keys()) {
const stringKey = String(key);
// Use the enhanced sensitive key detection with strictMode
if (!this.sensitiveKeysManager.isSensitive(stringKey, strictMode)) {
const value = this.get(key);
// If the value is a nested object, process it with the same strictMode
if (value &&
typeof value === "object" &&
value !== null &&
!Array.isArray(value)) {
// Process nested object with the same strict mode
const processedValue = this.processNestedObjectForFiltering(value, strictMode);
filtered.set(stringKey, processedValue);
}
else {
filtered.set(stringKey, value);
}
}
}
this.eventManager.emit("filtered", undefined, {
operation: "filterNonSensitive",
resultSize: filtered.size,
});
return filtered;
}
/**
* Processes nested objects for filtering with the same strict mode
*/
processNestedObjectForFiltering(obj, strictMode) {
if (Array.isArray(obj)) {
// Handle arrays
return obj.map((item) => typeof item === "object" && item !== null
? this.processNestedObjectForFiltering(item, strictMode)
: item);
}
else if (typeof obj === "object" && obj !== null) {
// Handle objects
const result = {};
for (const [key, value] of Object.entries(obj)) {
if (!this.sensitiveKeysManager.isSensitive(key, strictMode)) {
if (typeof value === "object" && value !== null) {
// Recursively process nested objects/arrays
result[key] = this.processNestedObjectForFiltering(value, strictMode);
}
else {
result[key] = value;
}
}
// If key is sensitive, skip it (don't add to result)
}
return result;
}
return obj;
}
// ===== METADATA OPERATIONS =====
/**
* Gets metadata for a specific key
*/
getMetadata(key) {
this.ensureNotDestroyed();
const stringKey = validation.ValidationUtils.sanitizeKey(key);
return this.metadataManager.get(stringKey);
}
/**
* Gets metadata for all keys
*/
getAllMetadata() {
this.ensureNotDestroyed();
return this.metadataManager.getAll();
}
// ===== SERIALIZATION METHODS =====
/**
* Converts to a regular object with security-focused serialization
*
* BEHAVIOR: This is the security-focused method that handles sensitive key filtering.
* Use this method when you need controlled access to data with security considerations.
* For simple object conversion without filtering, use toObject().
*
* @example
* const user = fObject({
id: "1",
email: "test@test.com",
password: "test123",
isVerified: true,
userName: "test",
firstName: "test",
lastName: "test",
bio: "test",
});
const getAllResult = user.getAll();
console.log("getAllResult.email:", getAllResult.email);
console.log("getAllResult.password:", getAllResult.password);
console.log("Has password?", "password" in getAllResult);
// Purpose: Security-conscious data access
// Behavior: Filters out sensitive keys by default
// Result: password: undefined (filtered for security)
// With encryptSensitive: true: ✔ password: "[ENCRYPTED:...]" (encrypted but included)
*/
getAll(options = {}) {
this.ensureNotDestroyed();
validation.ValidationUtils.validateSerializationOptions(options);
const sensitiveKeys = new Set(this.sensitiveKeysManager.getAll());
return this.serializationHandler.toObject(this.data, sensitiveKeys, options);
}
/**
* Gets the full object as a regular JavaScript object
*
* BEHAVIOR: Returns ALL data including sensitive keys (like standard JS object conversion).
* This method does NOT filter sensitive keys by default - use getAll() for security-focused serialization.
*
* @example
* const user = fObject({
id: "1",
email: "test@test.com",
password: "test123",
isVerified: true,
userName: "test",
firstName: "test",
lastName: "test",
bio: "test",
});
const toObjectResult = user.toObject();
console.log("toObjectResult.email:", toObjectResult.email);
console.log("toObjectResult.password:", toObjectResult.password);
console.log("Has password?", "password" in toObjectResult);
// Purpose: Standard JavaScript object conversion
// Behavior: Returns ALL data including sensitive keys (like password)
// Result: ✔ password: "test123" (included)
Sensitive keys can be handled using .add/removeSensitiveKeys()
*/
toObject(options) {
// toObject() should return ALL data by default (no filtering)
// Pass an empty Set to indicate no keys should be filtered
const noFiltering = new Set();
return this.serializationHandler.toObject(this.data, noFiltering, options);
}
/**
* Converts to JSON string
*/
toJSON(options = {}) {
this.ensureNotDestroyed();
validation.ValidationUtils.validateSerializationOptions(options);
// Use non-strict mode by default (only exact matches)
const strictMode = options.strictSensitiveKeys === true;
const isSensitiveKey = (key) => this.sensitiveKeysManager.isSensitive(key, strictMode);
return this.serializationHandler.toJSON(this.data, isSensitiveKey, options);
}
// ===== UTILITY METHODS =====
/**
* Creates a hash of the entire object content
*/
async hash(algorithm = "SHA-256", format = "hex") {
this.ensureNotDestroyed();
const serialized = this.serializationHandler.createHashableRepresentation(this.entries());
const secureString = new secureStringCore.SecureString(serialized);
try {
return await secureString.hash(algorithm, format);
}
finally {
secureString.destroy();
}
}
/**
* Executes a function with the object data and optionally clears it afterward
*/
use(fn, autoClear = false) {
this.ensureNotDestroyed();
validation.ValidationUtils.validateCallback(fn, "use function");
try {
return fn(this);
}
finally {
if (autoClear) {
this.destroy();
}
}
}
/**
* Creates a shallow copy of the SecureObject
*/
clone() {
this.ensureNotDestroyed();
return SecureObject.from(this);
}
/**
* Merges another object into this one
*/
merge(other, overwrite = true) {
this.ensureNotDestroyed();
this.ensureNotReadOnly();
if (other instanceof SecureObject) {
for (const key of other.keys()) {
const stringKey = String(key);
if (overwrite || !this.has(key)) {
this.set(stringKey, other.get(key));
}
}
}
else {
for (const key in other) {
if (Object.prototype.hasOwnProperty.call(other, key)) {
if (overwrite || !this.has(key)) {
this.set(String(key), other[key]);
}
}
}
}
return this;
}
// ===== AMAZING NEW FEATURES =====
/**
* Transform values with a mapper function (like Array.map but returns SecureObject)
* Returns a new SecureObject with transformed values
*/
transform(mapper) {
this.ensureNotDestroyed();
validation.ValidationUtils.validateMapper(mapper);
const transformed = new SecureObject();
for (const key of this.keys()) {
const value = this.get(key);
const newValue = mapper(value, key, this);
const stringKey = String(key);
transformed.set(stringKey, newValue);
}
this.eventManager.emit("filtered", undefined, {
operation: "transform",
resultSize: transformed.size,
});
return transformed;
}
/**
* Group entries by a classifier function
* Returns a Map where keys are group identifiers and values are SecureObjects
*/
groupBy(classifier) {
this.ensureNotDestroyed();
validation.ValidationUtils.validateCallback(classifier, "Classifier function");
const groups = new Map();
for (const key of this.keys()) {
const value = this.get(key);
const groupKey = classifier(value, key);
if (!groups.has(groupKey)) {
groups.set(groupKey, new SecureObject());
}
const group = groups.get(groupKey);
const stringKey = String(key);
group.set(stringKey, value);
}
this.eventManager.emit("filtered", undefined, {
operation: "groupBy",
resultSize: groups.size,
});
return groups;
}
/**
* Partition entries into two groups based on a predicate
* Returns [matching, notMatching] SecureObjects
*/
partition(predicate) {
this.ensureNotDestroyed();
validation.ValidationUtils.validatePredicate(predicate);
const matching = new SecureObject();
const notMatching = new SecureObject();
for (const key of this.keys()) {
const value = this.get(key);
const stringKey = String(key);
if (predicate(value, key)) {
matching.set(stringKey, value);
}
else {
notMatching.set(stringKey, value);
}
}
this.eventManager.emit("filtered", undefined, {
operation: "partition",
resultSize: matching.size + notMatching.size,
});
return [matching, notMatching];
}
/**
* Pick specific keys (like Lodash pick but type-safe)
* Returns a new SecureObject with only the specified keys
*/
pick(...keys) {
return this.filterByKeys(...keys);
}
/**
* Omit specific keys (opposite of pick)
* Returns a new SecureObject without the specified keys
*/
omit(...keys) {
this.ensureNotDestroyed();
const omitted = new SecureObject();
const keysToOmit = new Set(keys.map((k) => String(k)));
for (const key of this.keys()) {
const stringKey = String(key);
if (!keysToOmit.has(stringKey)) {
omitted.set(stringKey, this.get(key));
}
}
this.eventManager.emit("filtered", undefined, {
operation: "omit",
keys: keys.map((k) => String(k)),
resultSize: omitted.size,
});
return omitted;
}
/**
* Flatten nested objects (one level deep)
* Converts { user: { name: "John" } } to { "user.name": "John" }
*/
flatten(separator = ".") {
this.ensureNotDestroyed();
const flattened = new SecureObject();
for (const key of this.keys()) {
const value = this.get(key);
const stringKey = String(key);
if (value &&
typeof value === "object" &&
!Array.isArray(value) &&
!(value instanceof Date)) {
// Flatten nested object
for (const [nestedKey, nestedValue] of Object.entries(value)) {
const flatKey = `${stringKey}${separator}${nestedKey}`;
flattened.set(flatKey, nestedValue);
}
}
else {
flattened.set(stringKey, value);
}
}
this.eventManager.emit("filtered", undefined, {
operation: "flatten",
resultSize: flattened.size,
});
return flattened;
}
/**
* Compact - removes null, undefined, and empty values
* Returns a new SecureObject with only truthy values
*/
compact() {
return this.filter((value) => {
if (value === null || value === undefined)
return false;
if (typeof value === "string" && value.trim() === "")
return false;
if (Array.isArray(value) && value.length === 0)
return false;
if (typeof value === "object" && Object.keys(value).length === 0)
return false;
return true;
});
}
/**
* Invert - swap keys and values
* Returns a new SecureObject with keys and values swapped
*/
invert() {
this.ensureNotDestroyed();
const inverted = new SecureObject();
for (const key of this.keys()) {
const value = this.get(key);
const stringValue = String(value);
const stringKey = String(key);
inverted.set(stringValue, stringKey);
}
this.eventManager.emit("filtered", undefined, {
operation: "invert",
resultSize: inverted.size,
});
return inverted;
}
/**
* Defaults - merge with default values (only for missing keys)
* Returns a new SecureObject with defaults applied
*/
defaults(defaultValues) {
this.ensureNotDestroyed();
const result = new SecureObject();
// First, copy all existing values
for (const key of this.keys()) {
const stringKey = String(key);
result.set(stringKey, this.get(key));
}
// Then, add defaults for missing keys
for (const [key, value] of Object.entries(defaultValues)) {
if (!this.has(key)) {
result.set(key, value);
}
}
this.eventManager.emit("filtered", undefined, {
operation: "defaults",
resultSize: result.size,
});
return result;
}
/**
* Tap - execute a function with the object and return the object (for chaining)
* Useful for debugging or side effects in method chains
*/
tap(fn) {
this.ensureNotDestroyed();
validation.ValidationUtils.validateCallback(fn, "Tap function");
fn(this);
return this;
}
pipe(...fns) {
this.ensureNotDestroyed();
return fns.reduce((result, fn) => {
validation.ValidationUtils.validateCallback(fn, "Pipe function");
return fn(result);
}, this);
}
/**
* Sample - get random entries from the object
* Returns a new SecureObject with randomly selected entries
*/
sample(count = 1) {
this.ensureNotDestroyed();
if (count <= 0) {
return new SecureObject();
}
const allKeys = this.keys();
const sampleSize = Math.min(count, allKeys.length);
const sampledKeys = [];
// Simple random sampling without replacement
const availableKeys = [...allKeys];
for (let i = 0; i < sampleSize; i++) {
const randomIndex = Math.floor(Math.random() * availableKeys.length);
sampledKeys.push(availableKeys.splice(randomIndex, 1)[0]);
}
const sampled = new SecureObject();
for (const key of sampledKeys) {
const stringKey = String(key);
sampled.set(stri