content-guard
Version:
🛡️ Advanced content analysis and moderation system with multi-variant optimization. Features context-aware detection, harassment prevention, and ML-powered toxicity analysis. Pre-1.0 development version.
336 lines (287 loc) • 7.56 kB
JavaScript
/**
* Lightweight Utility Functions
*
* Replacement for heavy dependencies like lodash
*/
/**
* Simple email validation (replaces email-validator)
*/
function isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return emailRegex.test(email)
}
/**
* Calculate cosine similarity (replaces cosine-similarity package)
*/
function cosineSimilarity(vectorA, vectorB) {
if (vectorA.length !== vectorB.length) {
throw new Error('Vectors must have the same length')
}
let dotProduct = 0
let normA = 0
let normB = 0
for (let i = 0; i < vectorA.length; i++) {
dotProduct += vectorA[i] * vectorB[i]
normA += vectorA[i] * vectorA[i]
normB += vectorB[i] * vectorB[i]
}
if (normA === 0 || normB === 0) {
return 0
}
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB))
}
/**
* Simple string similarity (replaces string-similarity)
*/
function stringSimilarity(str1, str2) {
if (str1 === str2) return 1
if (str1.length === 0 || str2.length === 0) return 0
const longer = str1.length > str2.length ? str1 : str2
const shorter = str1.length > str2.length ? str2 : str1
if (longer.length === 0) return 1
return (longer.length - levenshteinDistance(longer, shorter)) / longer.length
}
/**
* Levenshtein distance calculation
*/
function levenshteinDistance(str1, str2) {
const matrix = Array(str2.length + 1).fill().map(() => Array(str1.length + 1).fill(0))
for (let i = 0; i <= str1.length; i++) matrix[0][i] = i
for (let j = 0; j <= str2.length; j++) matrix[j][0] = j
for (let j = 1; j <= str2.length; j++) {
for (let i = 1; i <= str1.length; i++) {
const indicator = str1[i - 1] === str2[j - 1] ? 0 : 1
matrix[j][i] = Math.min(
matrix[j][i - 1] + 1, // deletion
matrix[j - 1][i] + 1, // insertion
matrix[j - 1][i - 1] + indicator // substitution
)
}
}
return matrix[str2.length][str1.length]
}
/**
* Deep merge objects (replaces lodash.merge)
*/
function deepMerge(target, ...sources) {
if (!sources.length) return target
const source = sources.shift()
if (isObject(target) && isObject(source)) {
for (const key in source) {
if (isObject(source[key])) {
if (!target[key]) Object.assign(target, { [key]: {} })
deepMerge(target[key], source[key])
} else {
Object.assign(target, { [key]: source[key] })
}
}
}
return deepMerge(target, ...sources)
}
/**
* Check if value is object
*/
function isObject(item) {
return item && typeof item === 'object' && !Array.isArray(item)
}
/**
* Debounce function (replaces lodash.debounce)
*/
function debounce(func, delay) {
let timeoutId
return function (...args) {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => func.apply(this, args), delay)
}
}
/**
* Throttle function (replaces lodash.throttle)
*/
function throttle(func, delay) {
let inThrottle
return function (...args) {
if (!inThrottle) {
func.apply(this, args)
inThrottle = true
setTimeout(() => inThrottle = false, delay)
}
}
}
/**
* Flatten array (replaces lodash.flatten)
*/
function flatten(arr) {
return arr.reduce((flat, item) =>
Array.isArray(item) ? flat.concat(flatten(item)) : flat.concat(item), []
)
}
/**
* Unique array values (replaces lodash.uniq)
*/
function unique(arr) {
return [...new Set(arr)]
}
/**
* Clamp number between min and max
*/
function clamp(number, min, max) {
return Math.max(min, Math.min(number, max))
}
/**
* High-performance LRU Cache implementation
*/
class LRUCache {
constructor(maxSize = 100) {
this.maxSize = maxSize
this.cache = new Map()
}
get(key) {
if (this.cache.has(key)) {
const value = this.cache.get(key)
this.cache.delete(key)
this.cache.set(key, value)
return value
}
return undefined
}
set(key, value) {
if (this.cache.has(key)) {
this.cache.delete(key)
} else if (this.cache.size >= this.maxSize) {
const firstKey = this.cache.keys().next().value
this.cache.delete(firstKey)
}
this.cache.set(key, value)
}
has(key) {
return this.cache.has(key)
}
clear() {
this.cache.clear()
}
get size() {
return this.cache.size
}
}
/**
* High-performance xxHash-inspired hash function
* Optimized for strings with better collision resistance than simple hash
*/
function fastHash(str) {
if (!str || str.length === 0) return '0'
const PRIME32_1 = 0x9E3779B1
const PRIME32_2 = 0x85EBCA77
const PRIME32_3 = 0xC2B2AE3D
const PRIME32_4 = 0x27D4EB2F
const PRIME32_5 = 0x165667B1
let h32 = PRIME32_5 + str.length
let i = 0
// Process 4-byte chunks
while (i <= str.length - 4) {
let k1 = (str.charCodeAt(i) & 0xff) |
((str.charCodeAt(i + 1) & 0xff) << 8) |
((str.charCodeAt(i + 2) & 0xff) << 16) |
((str.charCodeAt(i + 3) & 0xff) << 24)
k1 = Math.imul(k1, PRIME32_3)
k1 = ((k1 << 17) | (k1 >>> 15))
k1 = Math.imul(k1, PRIME32_4)
h32 ^= k1
h32 = ((h32 << 19) | (h32 >>> 13))
h32 = Math.imul(h32, 5) + 0x561CCD1B
i += 4
}
// Process remaining bytes
while (i < str.length) {
let k1 = str.charCodeAt(i) & 0xff
k1 = Math.imul(k1, PRIME32_5)
k1 = ((k1 << 11) | (k1 >>> 21))
k1 = Math.imul(k1, PRIME32_1)
h32 ^= k1
h32 = ((h32 << 15) | (h32 >>> 17))
h32 = Math.imul(h32, PRIME32_2)
i++
}
// Final mix
h32 ^= h32 >>> 16
h32 = Math.imul(h32, PRIME32_2)
h32 ^= h32 >>> 13
h32 = Math.imul(h32, PRIME32_3)
h32 ^= h32 >>> 16
// Return as positive hex string
return (h32 >>> 0).toString(36)
}
/**
* Safe regex execution with timeout protection
*/
function safeRegexTest(regex, str, timeoutMs = 100) {
const start = Date.now()
try {
// Quick length check to prevent obvious ReDoS
if (str.length > 10000) {
str = str.substring(0, 10000)
}
const result = regex.test(str)
// Check if execution took too long
if (Date.now() - start > timeoutMs) {
console.warn('Regex execution took too long, possible ReDoS:', regex.source)
return false
}
return result
} catch (error) {
console.warn('Regex execution failed:', error.message)
return false
}
}
/**
* Memory-efficient string operations
*/
const StringUtils = {
/**
* Case-insensitive includes with early termination
*/
includesIgnoreCase(str, search) {
if (!str || !search) return false
return str.toLowerCase().indexOf(search.toLowerCase()) !== -1
},
/**
* Count occurrences of substring
*/
countOccurrences(str, search) {
if (!str || !search) return 0
let count = 0
let pos = 0
while ((pos = str.indexOf(search, pos)) !== -1) {
count++
pos += search.length
}
return count
},
/**
* Truncate string safely at word boundaries
*/
truncateAtWord(str, maxLength) {
if (str.length <= maxLength) return str
const truncated = str.substring(0, maxLength)
const lastSpace = truncated.lastIndexOf(' ')
return lastSpace > 0 ? truncated.substring(0, lastSpace) + '...' : truncated + '...'
}
}
module.exports = {
isValidEmail,
cosineSimilarity,
stringSimilarity,
levenshteinDistance,
deepMerge,
isObject,
debounce,
throttle,
flatten,
unique,
clamp,
LRUCache,
fastHash,
safeRegexTest,
StringUtils,
// Legacy aliases
simpleHash: fastHash
}