/** * Clean Metadata Architecture for Brainy 2.2 * No backward compatibility - doing it RIGHT from the start! */ // Namespace constants export const BRAINY_NS = '_brainy' as const export const AUG_NS = '_augmentations' as const export const AUDIT_NS = '_audit' as const // Field paths for O(1) indexing export const DELETED_FIELD = `${BRAINY_NS}.deleted` as const export const INDEXED_FIELD = `${BRAINY_NS}.indexed` as const export const VERSION_FIELD = `${BRAINY_NS}.version` as const /** * Internal Brainy metadata structure * These fields are ALWAYS present and indexed for O(1) access */ export interface BrainyInternalMetadata { deleted: boolean // ALWAYS boolean, enables O(1) soft delete indexed: boolean // Whether in search index version: number // Schema version created: number // Unix timestamp updated: number // Unix timestamp // Optional internal fields partition?: number // For distributed mode domain?: string // Domain classification priority?: number // Query priority hint ttl?: number // Time to live } /** * Complete metadata structure with namespaces */ export interface NamespacedMetadata { // User metadata - any fields they want [key: string]: any // Internal metadata - our fields [BRAINY_NS]: BrainyInternalMetadata // Augmentation metadata - isolated per augmentation [AUG_NS]?: { [augmentationName: string]: any } // Audit trail - optional [AUDIT_NS]?: Array<{ timestamp: number augmentation: string field: string oldValue: any newValue: any }> } /** * Create properly namespaced metadata * This is called for EVERY noun/verb creation */ export function createNamespacedMetadata( userMetadata?: T ): NamespacedMetadata { const now = Date.now() // Start with user metadata or empty object const result: any = userMetadata ? { ...userMetadata } : {} // ALWAYS add internal namespace with required fields result[BRAINY_NS] = { deleted: false, // CRITICAL: Always false for new items indexed: true, // New items are indexed version: 1, // Current schema version created: now, updated: now } return result } /** * Update metadata while preserving namespaces */ export function updateNamespacedMetadata( existing: NamespacedMetadata, updates: Partial ): NamespacedMetadata { const now = Date.now() // Merge user fields const result: any = { ...existing, ...updates } // Preserve internal namespace but update timestamp result[BRAINY_NS] = { ...existing[BRAINY_NS], updated: now } // Preserve augmentation namespace if (existing[AUG_NS]) { result[AUG_NS] = existing[AUG_NS] } // Preserve audit trail if (existing[AUDIT_NS]) { result[AUDIT_NS] = existing[AUDIT_NS] } return result } /** * Soft delete a noun (O(1) operation) */ export function markDeleted( metadata: NamespacedMetadata ): NamespacedMetadata { return { ...metadata, [BRAINY_NS]: { ...metadata[BRAINY_NS], deleted: true, updated: Date.now() } } } /** * Restore a soft-deleted noun (O(1) operation) */ export function markRestored( metadata: NamespacedMetadata ): NamespacedMetadata { return { ...metadata, [BRAINY_NS]: { ...metadata[BRAINY_NS], deleted: false, updated: Date.now() } } } /** * Check if a noun is deleted (O(1) check) */ export function isDeleted( metadata: NamespacedMetadata ): boolean { return metadata[BRAINY_NS]?.deleted === true } /** * Get user metadata without internal fields * Used by augmentations to get clean user data */ export function getUserMetadata( metadata: NamespacedMetadata ): T { const { [BRAINY_NS]: _, [AUG_NS]: __, [AUDIT_NS]: ___, ...userMeta } = metadata return userMeta as T } /** * Set augmentation data in isolated namespace */ export function setAugmentationData( metadata: NamespacedMetadata, augmentationName: string, data: any ): NamespacedMetadata { const result = { ...metadata } if (!result[AUG_NS]) { result[AUG_NS] = {} } result[AUG_NS][augmentationName] = data return result } /** * Add audit entry for tracking */ export function addAuditEntry( metadata: NamespacedMetadata, entry: { augmentation: string field: string oldValue: any newValue: any } ): NamespacedMetadata { const result = { ...metadata } if (!result[AUDIT_NS]) { result[AUDIT_NS] = [] } result[AUDIT_NS].push({ ...entry, timestamp: Date.now() }) return result } /** * INDEXING EXPLANATION: * * The MetadataIndex flattens nested objects into dot-notation keys: * * Input metadata: * { * name: "Django", * _brainy: { * deleted: false, * indexed: true * } * } * * Creates index entries: * - "name" -> "django" -> Set([id1, id2...]) * - "_brainy.deleted" -> "false" -> Set([id1, id2...]) // O(1) lookup! * - "_brainy.indexed" -> "true" -> Set([id1, id2...]) * * Query: { "_brainy.deleted": false } * Lookup: index["_brainy.deleted"]["false"] -> Set of IDs in O(1) * * This is why namespacing doesn't hurt performance - it's all flattened! */ /** * Fields that should ALWAYS be indexed for O(1) access */ export const ALWAYS_INDEXED_FIELDS = [ DELETED_FIELD, // For soft delete filtering INDEXED_FIELD, // For index management VERSION_FIELD // For schema versioning ] /** * Fields that should use sorted index for O(log n) range queries */ export const SORTED_INDEX_FIELDS = [ `${BRAINY_NS}.created`, `${BRAINY_NS}.updated`, `${BRAINY_NS}.priority`, `${BRAINY_NS}.ttl` ]