UNPKG

ai-seo

Version:

AI-native JSON-LD schema utility with automated URL-to-Schema generation, intelligent content analysis, caching, rate limiting, performance monitoring, and AI optimization (ChatGPT, Voice). Complete automation & scale features. Zero runtime dependencies.

1,796 lines (1,573 loc) 235 kB
// Simple AI-friendly SEO utility with enhanced features const performanceNow = (() => { const perf = globalThis.performance; return perf && typeof perf.now === 'function' ? perf.now.bind(perf) : Date.now; })(); // Global registry to track injected schemas const schemaRegistry = new Map(); // Advanced Caching System for v1.5.0 class SchemaCache { constructor() { this.cache = new Map(); this.config = { strategy: 'intelligent', // 'none', 'basic', 'intelligent' ttl: 3600000, // 1 hour default maxSize: 50, enableCompression: true, enableMetrics: true }; this.metrics = { hits: 0, misses: 0, compressionSavings: 0, averageAccessTime: 0 }; this.accessTimes = []; } configure(options = {}) { this.config = { ...this.config, ...options }; // Clear cache if strategy changed to 'none' if (this.config.strategy === 'none') { this.clear(); } // Enforce max size if (this.cache.size > this.config.maxSize) { this._enforceMaxSize(); } } _generateKey(schema, options = {}) { // Create a stable cache key based on schema content and options const keyData = { type: schema['@type'], content: JSON.stringify(schema), options: JSON.stringify(options) }; // Simple hash function for cache key return this._hash(JSON.stringify(keyData)); } _hash(str) { let hash = 0; if (str.length === 0) return hash.toString(); for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // Convert to 32-bit integer } return Math.abs(hash).toString(36); } _compress(data) { if (!this.config.enableCompression) return data; // Simple compression simulation (in real implementation, use actual compression) const original = JSON.stringify(data); const compressed = original.replace(/\s+/g, ' ').trim(); if (this.config.enableMetrics) { this.metrics.compressionSavings += (original.length - compressed.length); } return compressed; } _decompress(data) { if (!this.config.enableCompression) return data; return typeof data === 'string' ? JSON.parse(data) : data; } _enforceMaxSize() { if (this.cache.size <= this.config.maxSize) return; // LRU eviction: remove oldest entries const entries = Array.from(this.cache.entries()); entries.sort((a, b) => a[1].lastAccessed - b[1].lastAccessed); const toRemove = entries.slice(0, this.cache.size - this.config.maxSize); toRemove.forEach(([key]) => this.cache.delete(key)); } _isExpired(entry) { if (!this.config.ttl) return false; return Date.now() - entry.timestamp > this.config.ttl; } _recordAccess(hit = true) { const accessTime = performanceNow(); this.accessTimes.push(accessTime); if (this.config.enableMetrics) { if (hit) { this.metrics.hits++; } else { this.metrics.misses++; } // Keep only last 100 access times for average calculation if (this.accessTimes.length > 100) { this.accessTimes = this.accessTimes.slice(-50); // More aggressive cleanup } this.metrics.averageAccessTime = this.accessTimes.reduce((a, b) => a + b, 0) / this.accessTimes.length; } } get(schema, options = {}) { if (this.config.strategy === 'none') return null; const key = this._generateKey(schema, options); const entry = this.cache.get(key); if (!entry || this._isExpired(entry)) { this._recordAccess(false); if (entry) this.cache.delete(key); // Remove expired entry return null; } // Update last accessed time entry.lastAccessed = Date.now(); this.cache.set(key, entry); this._recordAccess(true); return this._decompress(entry.data); } set(schema, options = {}, result) { if (this.config.strategy === 'none') return false; const key = this._generateKey(schema, options); const compressedData = this._compress(result); const entry = { data: compressedData, timestamp: Date.now(), lastAccessed: Date.now(), schemaType: schema['@type'], size: JSON.stringify(result).length }; this.cache.set(key, entry); this._enforceMaxSize(); return true; } clear() { this.cache.clear(); this.metrics = { hits: 0, misses: 0, compressionSavings: 0, averageAccessTime: 0 }; this.accessTimes = []; } getMetrics() { const hitRate = this.metrics.hits + this.metrics.misses > 0 ? (this.metrics.hits / (this.metrics.hits + this.metrics.misses)) * 100 : 0; return { ...this.metrics, hitRate: Math.round(hitRate * 100) / 100, cacheSize: this.cache.size, maxSize: this.config.maxSize, strategy: this.config.strategy, totalEntries: this.metrics.hits + this.metrics.misses }; } // Intelligent caching strategies shouldCache(schema) { if (this.config.strategy === 'none') return false; if (this.config.strategy === 'basic') return true; // Intelligent strategy: cache based on schema complexity and reuse patterns if (this.config.strategy === 'intelligent') { const schemaSize = JSON.stringify(schema).length; const isComplex = schemaSize > 500; // Cache complex schemas const hasMultipleProperties = Object.keys(schema).length > 5; const isReusableType = ['Product', 'Article', 'LocalBusiness', 'Event'].includes(schema['@type']); return isComplex || hasMultipleProperties || isReusableType; } return true; } } // Global cache instance const globalSchemaCache = new SchemaCache(); // Cache configuration API export const Cache = { configure: (options) => globalSchemaCache.configure(options), clear: () => globalSchemaCache.clear(), getMetrics: () => globalSchemaCache.getMetrics(), getInstance: () => globalSchemaCache }; // Lazy Schema Loading System for v1.5.0 export class LazySchema { constructor(type = 'Thing') { this.schemaType = type; this.loadCondition = 'immediate'; // 'immediate', 'visible', 'interaction', 'custom' this.dataProvider = null; this.element = null; this.observer = null; this.loaded = false; this.config = { rootMargin: '50px', threshold: 0.1, debug: false }; } // Configure when to load the schema loadWhen(condition, customFn = null) { this.loadCondition = condition; if (condition === 'custom' && typeof customFn === 'function') { this.customCondition = customFn; } return this; } // Set data provider function withData(dataFn) { if (typeof dataFn === 'function') { this.dataProvider = dataFn; } return this; } // Configure lazy loading options configure(options = {}) { this.config = { ...this.config, ...options }; return this; } // Create placeholder element for visibility tracking _createPlaceholder() { if (!isBrowserEnvironment()) return null; const placeholder = document.createElement('div'); placeholder.setAttribute('data-lazy-schema', 'true'); placeholder.setAttribute('data-schema-type', this.schemaType); placeholder.style.height = '1px'; placeholder.style.width = '1px'; placeholder.style.position = 'absolute'; placeholder.style.top = '0'; placeholder.style.left = '0'; placeholder.style.pointerEvents = 'none'; placeholder.style.opacity = '0'; return placeholder; } // Load schema immediately _loadImmediate() { return this._executeLoad(); } // Setup visibility observer _setupVisibilityObserver() { if (!isBrowserEnvironment() || !window.IntersectionObserver) { // Fallback to immediate loading if IntersectionObserver not available return this._loadImmediate(); } this.element = this._createPlaceholder(); if (!this.element) return null; document.body.appendChild(this.element); this.observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting && !this.loaded) { this._executeLoad(); this.observer.disconnect(); this.element.remove(); } }); }, { rootMargin: this.config.rootMargin, threshold: this.config.threshold }); this.observer.observe(this.element); return this.element; } // Setup interaction observer _setupInteractionObserver() { if (!isBrowserEnvironment()) { return this._loadImmediate(); } const events = ['click', 'scroll', 'keydown', 'touchstart']; const loadOnInteraction = () => { if (!this.loaded) { this._executeLoad(); events.forEach(event => { document.removeEventListener(event, loadOnInteraction, { passive: true }); }); } }; events.forEach(event => { document.addEventListener(event, loadOnInteraction, { passive: true }); }); return { type: 'interaction-listener', events }; } // Execute the actual schema loading _executeLoad() { if (this.loaded) return null; try { const data = this.dataProvider ? this.dataProvider() : {}; const schema = this._buildSchema(data); const result = initSEO({ schema, debug: this.config.debug, id: `lazy-${this.schemaType.toLowerCase()}-${Date.now()}` }); this.loaded = true; if (this.config.debug) { debugLog(`Lazy loaded ${this.schemaType} schema`, 'info', true); } return result; } catch (error) { debugLog(`Error in lazy schema loading: ${error.message}`, 'error', this.config.debug); return null; } } // Build schema from data _buildSchema(data) { const baseSchema = { "@context": "https://schema.org", "@type": this.schemaType }; // Merge with provided data return { ...baseSchema, ...data }; } // Custom condition checking _checkCustomCondition() { if (typeof this.customCondition === 'function') { try { return this.customCondition(); } catch (error) { debugLog(`Error in custom condition: ${error.message}`, 'error', this.config.debug); return true; // Fallback to loading } } return true; } // Main injection method inject(options = {}) { this.config = { ...this.config, ...options }; switch (this.loadCondition) { case 'immediate': return this._loadImmediate(); case 'visible': return this._setupVisibilityObserver(); case 'interaction': return this._setupInteractionObserver(); case 'custom': if (this._checkCustomCondition()) { return this._loadImmediate(); } return null; default: return this._loadImmediate(); } } // Cleanup method cleanup() { if (this.observer) { this.observer.disconnect(); this.observer = null; } if (this.element && this.element.parentNode) { this.element.parentNode.removeChild(this.element); this.element = null; } } } // Performance Monitoring System for v1.5.0 export const Performance = { metrics: { schemaInjections: [], averageInjectionTime: 0, cacheHitRate: 0, totalSchemas: 0, performanceScore: 100 }, // Record schema injection performance recordInjection(duration, fromCache = false, schemaType = 'Unknown') { const metric = { duration, fromCache, schemaType, timestamp: Date.now() }; this.metrics.schemaInjections.push(metric); // Keep only last 100 measurements if (this.metrics.schemaInjections.length > 100) { this.metrics.schemaInjections = this.metrics.schemaInjections.slice(-100); } this._updateMetrics(); }, // Update calculated metrics _updateMetrics() { const injections = this.metrics.schemaInjections; if (injections.length === 0) return; // Calculate average injection time const totalTime = injections.reduce((sum, m) => sum + m.duration, 0); this.metrics.averageInjectionTime = totalTime / injections.length; // Calculate cache hit rate const cacheHits = injections.filter(m => m.fromCache).length; this.metrics.cacheHitRate = (cacheHits / injections.length) * 100; // Update total schemas this.metrics.totalSchemas = injections.length; // Calculate performance score this._calculatePerformanceScore(); }, // Calculate overall performance score _calculatePerformanceScore() { let score = 100; // Deduct points for slow injections if (this.metrics.averageInjectionTime > 5) score -= 20; else if (this.metrics.averageInjectionTime > 2) score -= 10; // Add points for good cache hit rate if (this.metrics.cacheHitRate > 80) score += 10; else if (this.metrics.cacheHitRate < 20) score -= 15; // Deduct points for too many schemas (performance impact) if (this.metrics.totalSchemas > 50) score -= 10; this.metrics.performanceScore = Math.max(0, Math.min(100, score)); }, // Get performance report getReport() { const report = { ...this.metrics, recommendations: this._generateRecommendations(), timestamp: new Date().toISOString() }; return report; }, // Generate performance recommendations _generateRecommendations() { const recommendations = []; if (this.metrics.averageInjectionTime > 5) { recommendations.push({ type: 'performance', severity: 'high', message: 'Schema injection time is slow. Consider enabling caching or reducing schema complexity.', action: 'Enable intelligent caching: Cache.configure({ strategy: "intelligent" })' }); } if (this.metrics.cacheHitRate < 30 && this.metrics.totalSchemas > 10) { recommendations.push({ type: 'caching', severity: 'medium', message: 'Low cache hit rate detected. Review caching strategy.', action: 'Optimize for reusable schemas or increase cache size: Cache.configure({ maxSize: 100 })' }); } if (this.metrics.totalSchemas > 20) { recommendations.push({ type: 'optimization', severity: 'medium', message: 'Many schemas detected. Consider lazy loading for better performance.', action: 'Use LazySchema for non-critical schemas: new LazySchema("Product").loadWhen("visible")' }); } if (this.metrics.performanceScore > 90) { recommendations.push({ type: 'success', severity: 'info', message: 'Excellent performance! Your schema implementation is optimized.', action: 'Continue monitoring performance as your application grows.' }); } return recommendations; }, // Clear performance data clear() { this.metrics = { schemaInjections: [], averageInjectionTime: 0, cacheHitRate: 0, totalSchemas: 0, performanceScore: 100 }; } }; // Schema builder helpers for common schema types export const SchemaHelpers = { /** * Create a Product schema */ createProduct({ name, description, image, brand, offers = {}, aggregateRating, review, sku, mpn, gtin, ...additionalProps } = {}) { const schema = { "@context": "https://schema.org", "@type": "Product", name, description, ...additionalProps }; if (image) schema.image = Array.isArray(image) ? image : [image]; if (brand) schema.brand = typeof brand === 'string' ? { "@type": "Brand", name: brand } : brand; if (sku) schema.sku = sku; if (mpn) schema.mpn = mpn; if (gtin) schema.gtin = gtin; if (offers && Object.keys(offers).length > 0) { schema.offers = { "@type": "Offer", priceCurrency: offers.priceCurrency || "USD", price: offers.price, availability: offers.availability || "https://schema.org/InStock", ...offers }; } if (aggregateRating) { schema.aggregateRating = { "@type": "AggregateRating", ratingValue: aggregateRating.ratingValue, reviewCount: aggregateRating.reviewCount, ...aggregateRating }; } if (review) { schema.review = Array.isArray(review) ? review.map(r => ({ "@type": "Review", reviewRating: r.rating ? { "@type": "Rating", ratingValue: r.rating } : undefined, author: r.author ? { "@type": "Person", name: r.author } : undefined, ...r })) : [review]; } return schema; }, /** * Create an Article schema */ createArticle({ headline, description, author, datePublished, dateModified, image, publisher, url, wordCount, articleSection, keywords, ...additionalProps } = {}) { const schema = { "@context": "https://schema.org", "@type": "Article", headline, description, ...additionalProps }; if (author) { schema.author = typeof author === 'string' ? { "@type": "Person", name: author } : author; } if (datePublished) schema.datePublished = datePublished; if (dateModified) schema.dateModified = dateModified; if (image) schema.image = Array.isArray(image) ? image : [image]; if (url) schema.url = url; if (wordCount) schema.wordCount = wordCount; if (articleSection) schema.articleSection = articleSection; if (keywords) schema.keywords = Array.isArray(keywords) ? keywords : [keywords]; if (publisher) { schema.publisher = typeof publisher === 'string' ? { "@type": "Organization", name: publisher } : publisher; } return schema; }, /** * Create a LocalBusiness schema */ createLocalBusiness({ name, description, address, telephone, openingHours, url, image, priceRange, aggregateRating, geo, businessType = "LocalBusiness", ...additionalProps } = {}) { const schema = { "@context": "https://schema.org", "@type": businessType, name, description, ...additionalProps }; if (address) { schema.address = typeof address === 'string' ? { "@type": "PostalAddress", streetAddress: address } : { "@type": "PostalAddress", ...address }; } if (telephone) schema.telephone = telephone; if (url) schema.url = url; if (image) schema.image = Array.isArray(image) ? image : [image]; if (priceRange) schema.priceRange = priceRange; if (openingHours) { schema.openingHours = Array.isArray(openingHours) ? openingHours : [openingHours]; } if (aggregateRating) { schema.aggregateRating = { "@type": "AggregateRating", ratingValue: aggregateRating.ratingValue, reviewCount: aggregateRating.reviewCount, ...aggregateRating }; } if (geo) { schema.geo = { "@type": "GeoCoordinates", latitude: geo.latitude, longitude: geo.longitude, ...geo }; } return schema; }, /** * Create an Event schema */ createEvent({ name, description, startDate, endDate, location, organizer, offers, image, url, eventStatus = "https://schema.org/EventScheduled", eventAttendanceMode = "https://schema.org/OfflineEventAttendanceMode", ...additionalProps } = {}) { const schema = { "@context": "https://schema.org", "@type": "Event", name, description, startDate, eventStatus, eventAttendanceMode, ...additionalProps }; if (endDate) schema.endDate = endDate; if (url) schema.url = url; if (image) schema.image = Array.isArray(image) ? image : [image]; if (location) { if (typeof location === 'string') { schema.location = { "@type": "Place", name: location }; } else if (location.address || location.geo) { schema.location = { "@type": "Place", name: location.name, address: location.address ? { "@type": "PostalAddress", ...(typeof location.address === 'string' ? { streetAddress: location.address } : location.address) } : undefined, geo: location.geo ? { "@type": "GeoCoordinates", latitude: location.geo.latitude, longitude: location.geo.longitude } : undefined, ...location }; } else { schema.location = { "@type": "Place", ...location }; } } if (organizer) { schema.organizer = typeof organizer === 'string' ? { "@type": "Organization", name: organizer } : organizer; } if (offers) { schema.offers = Array.isArray(offers) ? offers.map(offer => ({ "@type": "Offer", priceCurrency: offer.priceCurrency || "USD", ...offer })) : { "@type": "Offer", priceCurrency: offers.priceCurrency || "USD", ...offers }; } return schema; } }; // Schema Composer API - Fluent interface for complex schema building export class SchemaComposer { constructor(type = 'Thing') { this.schema = { "@context": "https://schema.org", "@type": type }; } // Core properties name(value) { this.schema.name = value; return this; } description(value) { this.schema.description = value; return this; } url(value) { this.schema.url = value; return this; } image(value) { this.schema.image = Array.isArray(value) ? value : [value]; return this; } // Organization/Business specific address(value) { if (typeof value === 'string') { this.schema.address = { "@type": "PostalAddress", streetAddress: value }; } else { this.schema.address = { "@type": "PostalAddress", ...value }; } return this; } telephone(value) { this.schema.telephone = value; return this; } email(value) { this.schema.email = value; return this; } // Product specific brand(value) { this.schema.brand = typeof value === 'string' ? { "@type": "Brand", name: value } : value; return this; } offers(priceOptions) { if (Array.isArray(priceOptions)) { this.schema.offers = priceOptions.map(offer => ({ "@type": "Offer", priceCurrency: offer.priceCurrency || "USD", ...offer })); } else { this.schema.offers = { "@type": "Offer", priceCurrency: priceOptions.priceCurrency || "USD", ...priceOptions }; } return this; } // Article specific author(value) { this.schema.author = typeof value === 'string' ? { "@type": "Person", name: value } : value; return this; } publisher(value) { this.schema.publisher = typeof value === 'string' ? { "@type": "Organization", name: value } : value; return this; } datePublished(value) { this.schema.datePublished = value; return this; } dateModified(value) { this.schema.dateModified = value; return this; } keywords(value) { this.schema.keywords = Array.isArray(value) ? value : [value]; return this; } // Event specific startDate(value) { this.schema.startDate = value; return this; } endDate(value) { this.schema.endDate = value; return this; } location(value) { if (typeof value === 'string') { this.schema.location = { "@type": "Place", name: value }; } else { this.schema.location = { "@type": "Place", ...value }; } return this; } organizer(value) { this.schema.organizer = typeof value === 'string' ? { "@type": "Organization", name: value } : value; return this; } // Rating and reviews rating(ratingValue, reviewCount, bestRating = 5, worstRating = 1) { this.schema.aggregateRating = { "@type": "AggregateRating", ratingValue, reviewCount, bestRating, worstRating }; return this; } review(reviewData) { if (!this.schema.review) this.schema.review = []; const reviews = Array.isArray(reviewData) ? reviewData : [reviewData]; this.schema.review.push(...reviews.map(review => ({ "@type": "Review", reviewRating: review.rating ? { "@type": "Rating", ratingValue: review.rating } : undefined, author: review.author ? { "@type": "Person", name: review.author } : undefined, ...review }))); return this; } // Geographic coordinates geo(latitude, longitude) { this.schema.geo = { "@type": "GeoCoordinates", latitude, longitude }; return this; } // Opening hours for businesses openingHours(hours) { this.schema.openingHours = Array.isArray(hours) ? hours : [hours]; return this; } // Price range priceRange(range) { this.schema.priceRange = range; return this; } // Custom properties addProperty(key, value) { this.schema[key] = value; return this; } // Merge with another schema merge(otherSchema) { this.schema = { ...this.schema, ...otherSchema }; return this; } // Build and return the schema build() { return { ...this.schema }; } // Build and inject into DOM inject(options = {}) { const schema = this.build(); return initSEO({ schema, ...options }); } } // Convenience factory functions export const createSchema = (type) => new SchemaComposer(type); export const product = () => new SchemaComposer('Product'); export const article = () => new SchemaComposer('Article'); export const organization = () => new SchemaComposer('Organization'); export const localBusiness = (businessType = 'LocalBusiness') => new SchemaComposer(businessType); export const event = () => new SchemaComposer('Event'); export const person = () => new SchemaComposer('Person'); export const website = () => new SchemaComposer('WebSite'); export const webpage = () => new SchemaComposer('WebPage'); // Framework Integrations export const Frameworks = { /** * React Hooks */ React: { // Hook for managing single schema useSEO: (schemaOrFunction) => { // Note: In real React environment, this would use actual React hooks // This is a compatible interface that works in any environment let currentSchema = null; let cleanup = null; const updateSchema = () => { // Clean up previous schema if (cleanup) cleanup(); // Get new schema const schema = typeof schemaOrFunction === 'function' ? schemaOrFunction() : schemaOrFunction; if (schema) { const element = initSEO({ schema }); if (element) { cleanup = () => { if (element.parentNode) { element.parentNode.removeChild(element); } }; currentSchema = schema; } } }; // Initial setup updateSchema(); return { schema: currentSchema, update: updateSchema, cleanup: () => cleanup && cleanup() }; }, // Hook for managing multiple schemas useMultipleSEO: (schemasOrFunction) => { let currentElements = []; let cleanup = null; const updateSchemas = () => { // Clean up previous schemas if (cleanup) cleanup(); const schemas = typeof schemasOrFunction === 'function' ? schemasOrFunction() : schemasOrFunction; if (schemas && schemas.length > 0) { const results = injectMultipleSchemas(schemas); currentElements = results.filter(r => r.success).map(r => r.element); cleanup = () => { currentElements.forEach(el => { if (el && el.parentNode) { el.parentNode.removeChild(el); } }); currentElements = []; }; } }; updateSchemas(); return { elements: currentElements, update: updateSchemas, cleanup: () => cleanup && cleanup() }; }, // HOC for easy schema injection withSEO: (Component, schemaOrFunction) => { return (props) => { const schema = typeof schemaOrFunction === 'function' ? schemaOrFunction(props) : schemaOrFunction; if (schema) { initSEO({ schema }); } return Component(props); }; } }, /** * Vue Composables */ Vue: { // Composable for managing single schema useSEO: (schemaOrRef, options = {}) => { let currentElement = null; let cleanup = null; const updateSchema = () => { if (cleanup) cleanup(); const schema = typeof schemaOrRef === 'function' ? schemaOrRef() : (schemaOrRef?.value || schemaOrRef); if (schema) { const element = initSEO({ schema, ...options }); if (element) { currentElement = element; cleanup = () => { if (element.parentNode) { element.parentNode.removeChild(element); } }; } } }; updateSchema(); return { element: currentElement, update: updateSchema, cleanup: () => cleanup && cleanup() }; }, // Composable for multiple schemas useMultipleSEO: (schemasOrRef, options = {}) => { let currentElements = []; let cleanup = null; const updateSchemas = () => { if (cleanup) cleanup(); const schemas = typeof schemasOrRef === 'function' ? schemasOrRef() : (schemasOrRef?.value || schemasOrRef); if (schemas && schemas.length > 0) { const results = injectMultipleSchemas(schemas, options); currentElements = results.filter(r => r.success).map(r => r.element); cleanup = () => { currentElements.forEach(el => { if (el && el.parentNode) { el.parentNode.removeChild(el); } }); currentElements = []; }; } }; updateSchemas(); return { elements: currentElements, update: updateSchemas, cleanup: () => cleanup && cleanup() }; } }, /** * Svelte Stores */ Svelte: { // Schema store createSEOStore: (initialSchema = null) => { const subscribers = []; let currentSchema = initialSchema; let currentElement = null; const subscribe = (callback) => { subscribers.push(callback); callback(currentSchema); return () => { const index = subscribers.indexOf(callback); if (index > -1) subscribers.splice(index, 1); }; }; const set = (schema) => { // Clean up previous if (currentElement && currentElement.parentNode) { currentElement.parentNode.removeChild(currentElement); } currentSchema = schema; // Inject new schema if (schema) { currentElement = initSEO({ schema }); } // Notify subscribers subscribers.forEach(callback => callback(currentSchema)); }; const update = (updater) => { set(updater(currentSchema)); }; // Initial injection if (initialSchema) { currentElement = initSEO({ schema: initialSchema }); } return { subscribe, set, update, get: () => currentSchema }; }, // Multiple schemas store createMultipleSEOStore: (initialSchemas = []) => { const subscribers = []; let currentSchemas = initialSchemas; let currentElements = []; const subscribe = (callback) => { subscribers.push(callback); callback(currentSchemas); return () => { const index = subscribers.indexOf(callback); if (index > -1) subscribers.splice(index, 1); }; }; const set = (schemas) => { // Clean up previous currentElements.forEach(el => { if (el && el.parentNode) { el.parentNode.removeChild(el); } }); currentSchemas = schemas; // Inject new schemas if (schemas && schemas.length > 0) { const results = injectMultipleSchemas(schemas); currentElements = results.filter(r => r.success).map(r => r.element); } else { currentElements = []; } // Notify subscribers subscribers.forEach(callback => callback(currentSchemas)); }; const update = (updater) => { set(updater(currentSchemas)); }; // Initial injection if (initialSchemas.length > 0) { const results = injectMultipleSchemas(initialSchemas); currentElements = results.filter(r => r.success).map(r => r.element); } return { subscribe, set, update, get: () => currentSchemas }; } } }; // Schema Templates - Pre-built templates for common industries export const Templates = { /** * E-commerce Templates */ ecommerce: { // Product listing page productPage: (productData) => { return product() .name(productData.name) .description(productData.description) .image(productData.images || productData.image) .brand(productData.brand) .offers({ price: productData.price, priceCurrency: productData.currency || 'USD', availability: productData.inStock ? 'https://schema.org/InStock' : 'https://schema.org/OutOfStock', seller: productData.seller }) .rating(productData.rating, productData.reviewCount) .addProperty('sku', productData.sku) .addProperty('gtin', productData.gtin || productData.upc) .build(); }, // Online store onlineStore: (storeData) => { return organization() .name(storeData.name) .description(storeData.description) .url(storeData.website) .email(storeData.email) .telephone(storeData.phone) .addProperty('@type', 'OnlineStore') .addProperty('paymentAccepted', storeData.paymentMethods || ['Credit Card', 'PayPal']) .addProperty('currenciesAccepted', storeData.currencies || ['USD']) .build(); } }, /** * Restaurant & Food Service Templates */ restaurant: { // Restaurant listing restaurant: (restaurantData) => { return localBusiness('Restaurant') .name(restaurantData.name) .description(restaurantData.description) .address(restaurantData.address) .telephone(restaurantData.phone) .url(restaurantData.website) .openingHours(restaurantData.hours) .priceRange(restaurantData.priceRange || '$$') .rating(restaurantData.rating, restaurantData.reviewCount) .geo(restaurantData.latitude, restaurantData.longitude) .addProperty('servesCuisine', restaurantData.cuisine) .addProperty('acceptsReservations', restaurantData.acceptsReservations || true) .build(); }, // Menu item menuItem: (itemData) => { return createSchema('MenuItem') .name(itemData.name) .description(itemData.description) .image(itemData.image) .offers({ price: itemData.price, priceCurrency: itemData.currency || 'USD' }) .addProperty('nutrition', itemData.nutrition) .addProperty('suitableForDiet', itemData.dietaryRestrictions) .build(); } }, /** * Healthcare Templates */ healthcare: { // Medical practice medicalOrganization: (practiceData) => { return localBusiness('MedicalOrganization') .name(practiceData.name) .description(practiceData.description) .address(practiceData.address) .telephone(practiceData.phone) .url(practiceData.website) .openingHours(practiceData.hours) .addProperty('medicalSpecialty', practiceData.specialties) .addProperty('acceptedInsurance', practiceData.insurance) .build(); }, // Healthcare provider physician: (doctorData) => { return person() .name(doctorData.name) .addProperty('@type', 'Physician') .addProperty('medicalSpecialty', doctorData.specialties) .addProperty('affiliation', doctorData.hospital) .addProperty('alumniOf', doctorData.education) .addProperty('availableService', doctorData.services) .build(); } }, /** * Real Estate Templates */ realEstate: { // Property listing realEstateProperty: (propertyData) => { return createSchema('RealEstateListing') .name(propertyData.title) .description(propertyData.description) .image(propertyData.images) .address(propertyData.address) .addProperty('listingAgent', { '@type': 'RealEstateAgent', name: propertyData.agent.name, telephone: propertyData.agent.phone }) .addProperty('price', { '@type': 'PriceSpecification', price: propertyData.price, priceCurrency: propertyData.currency || 'USD' }) .addProperty('numberOfRooms', propertyData.bedrooms) .addProperty('numberOfBathroomsTotal', propertyData.bathrooms) .addProperty('floorSize', propertyData.squareFeet) .build(); }, // Real estate agency realEstateAgency: (agencyData) => { return localBusiness('RealEstateAgent') .name(agencyData.name) .description(agencyData.description) .address(agencyData.address) .telephone(agencyData.phone) .url(agencyData.website) .email(agencyData.email) .addProperty('areaServed', agencyData.serviceAreas) .build(); } }, /** * Education Templates */ education: { // Educational institution school: (schoolData) => { return organization() .name(schoolData.name) .description(schoolData.description) .address(schoolData.address) .telephone(schoolData.phone) .url(schoolData.website) .addProperty('@type', 'EducationalOrganization') .addProperty('foundingDate', schoolData.founded) .addProperty('numberOfStudents', schoolData.enrollment) .build(); }, // Online course course: (courseData) => { return createSchema('Course') .name(courseData.title) .description(courseData.description) .addProperty('provider', { '@type': 'Organization', name: courseData.provider }) .addProperty('courseCode', courseData.code) .addProperty('educationalLevel', courseData.level) .addProperty('timeRequired', courseData.duration) .offers({ price: courseData.price || 0, priceCurrency: courseData.currency || 'USD' }) .build(); } }, /** * Professional Services Templates */ professional: { // Law firm lawFirm: (firmData) => { return localBusiness('LegalService') .name(firmData.name) .description(firmData.description) .address(firmData.address) .telephone(firmData.phone) .url(firmData.website) .email(firmData.email) .addProperty('areaOfLaw', firmData.practiceAreas) .addProperty('jurisdiction', firmData.jurisdictions) .build(); }, // Consulting firm consultingFirm: (firmData) => { return localBusiness('ProfessionalService') .name(firmData.name) .description(firmData.description) .address(firmData.address) .telephone(firmData.phone) .url(firmData.website) .email(firmData.email) .addProperty('serviceType', firmData.services) .addProperty('areaServed', firmData.serviceAreas) .build(); } }, /** * Event Templates */ events: { // Conference conference: (eventData) => { return event() .name(eventData.name) .description(eventData.description) .startDate(eventData.startDate) .endDate(eventData.endDate) .location({ name: eventData.venue, address: eventData.address }) .organizer(eventData.organizer) .offers({ price: eventData.ticketPrice, priceCurrency: eventData.currency || 'USD', url: eventData.ticketUrl }) .addProperty('eventAttendanceMode', eventData.isVirtual ? 'https://schema.org/OnlineEventAttendanceMode' : 'https://schema.org/OfflineEventAttendanceMode') .build(); }, // Workshop workshop: (workshopData) => { return event() .name(workshopData.name) .description(workshopData.description) .startDate(workshopData.startDate) .endDate(workshopData.endDate) .location(workshopData.location) .organizer(workshopData.instructor) .addProperty('maximumAttendeeCapacity', workshopData.maxAttendees) .addProperty('educationalLevel', workshopData.level) .offers({ price: workshopData.price, priceCurrency: workshopData.currency || 'USD' }) .build(); } }, /** * Job & Career Templates */ jobs: { // Job posting jobPosting: (jobData) => { return createSchema('JobPosting') .name(jobData.title) .description(jobData.description) .addProperty('datePosted', jobData.datePosted || new Date().toISOString()) .addProperty('validThrough', jobData.validThrough) .addProperty('employmentType', jobData.employmentType || 'FULL_TIME') .addProperty('hiringOrganization', { '@type': 'Organization', name: jobData.company, sameAs: jobData.companyWebsite, logo: jobData.companyLogo }) .addProperty('jobLocation', { '@type': 'Place', address: { '@type': 'PostalAddress', streetAddress: jobData.location?.address, addressLocality: jobData.location?.city, addressRegion: jobData.location?.state, postalCode: jobData.location?.zipCode, addressCountry: jobData.location?.country || 'US' } }) .addProperty('baseSalary', jobData.salary ? { '@type': 'MonetaryAmount', currency: jobData.salary.currency || 'USD', value: { '@type': 'QuantitativeValue', minValue: jobData.salary.min, maxValue: jobData.salary.max, unitText: jobData.salary.period || 'YEAR' } } : undefined) .addProperty('workFromHome', jobData.remote) .addProperty('qualifications', jobData.requirements) .addProperty('responsibilities', jobData.responsibilities) .addProperty('skills', jobData.skills) .addProperty('benefits', jobData.benefits) .addProperty('industry', jobData.industry) .addProperty('occupationalCategory', jobData.category) .build(); }, // Company profile company: (companyData) => { return organization() .name(companyData.name) .description(companyData.description) .url(companyData.website) .address(companyData.address) .telephone(companyData.phone) .email(companyData.email) .addProperty('logo', companyData.logo) .addProperty('foundingDate', companyData.founded) .addProperty('numberOfEmployees', companyData.employeeCount) .addProperty('industry', companyData.industry) .addProperty('slogan', companyData.slogan) .addProperty('awards', companyData.awards) .build(); } }, /** * Recipe & Food Templates */ recipe: { // Recipe schema recipe: (recipeData) => { return createSchema('Recipe') .name(recipeData.name) .description(recipeData.description) .image(recipeData.images) .author(recipeData.author) .datePublished(recipeData.datePublished) .addProperty('recipeYield', recipeData.servings) .addProperty('prepTime', recipeData.prepTime) // ISO 8601 duration .addProperty('cookTime', recipeData.cookTime) .addProperty('totalTime', recipeData.totalTime) .addProperty('recipeCategory', recipeData.category) .addProperty('recipeCuisine', recipeData.cuisine) .addProperty('keywords', recipeData.keywords) .addProperty('recipeIngredient', recipeData.ingredients) .addProperty('recipeInstructions', recipeData.instructions?.map(step => ({ '@type': 'HowToStep', text: step.text, image: step.image, name: step.name }))) .addProperty('nutrition', recipeData.nutrition ? { '@type': 'NutritionInformation', calories: recipeData.nutrition.calories, proteinContent: recipeData.nutrition.protein, fatContent: recipeData.nutrition.fat, carbohydrateContent: recipeData.nutrition.carbs, fiberContent: recipeData.nutrition.fiber, sugarContent: recipeData.nutrition.sugar, sodiumContent: recipeData.nutrition.sodium } : undefined) .rating(recipeData.rating, recipeData.reviewCount) .addProperty('video', recipeData.videoUrl ? { '@type': 'VideoObject', name: recipeData.name, description: recipeData.description, contentUrl: recipeData.videoUrl, thumbnailUrl: recipeData.videoThumbnail } : undefined) .build(); }, // Restaurant menu menu: (menuData) => { const menuSections = menuData.sections?.map(section => ({ '@type': 'MenuSection', name: section.name, description: section.description, hasMenuItem: section.items?.map(item => ({ '@type': 'MenuItem', name: item.name, description: item.description, offers: { '@type': 'Offer', price: item.price, priceCurrency: item.currency || 'USD' }, nutrition: item.nutrition, suitableForDiet: item.dietaryRestrictions })) })); return createSchema('Menu') .name(menuData.name) .description(menuData.description) .addProperty('hasMenuSection', menuSections) .addProperty('provider', { '@type': 'Restaurant', name: menuData.restaurant }) .build(); } }, /** * Media & Content Templates */ media: { // Video content video: (videoData) => { return createSchema('VideoObject') .name(videoData.title) .description(videoData.description) .addProperty('contentUrl', videoData.videoUrl) .addProperty('embedUrl', videoData.embedUrl) .addProperty('thumbnailUrl', videoData.thumbnail) .addProperty('uploadDate', videoData.uploadDate) .addProperty('duration', videoData.duration) // ISO 8601 duration .addProperty('contentRating', videoData.rating) .addProperty('genre', videoData.genre) .addProperty('keywords', videoData.tags) .addProperty('creator', { '@type': 'Person', name: videoData.creator, url: videoData.creatorUrl }) .addProperty('publisher', { '@type': 'Organization', name: videoData.publisher, logo: videoData.publisherLogo }) .addProperty('interactionStatistic', [ { '@type': 'InteractionCounter', interactionType: 'https://schema.org/WatchAction', userInteractionCount: videoData.viewCount }, { '@type': 'InteractionCounter', interactionType: 'https://schema.org/LikeAction', userInteractionCount: videoData.likeCount } ]) .build(); }, // Podcast episode podcast: (episodeData) => { return createSchema('PodcastEpisode') .name(episodeData.title) .description(episodeData.description) .url(episodeData.url) .addProperty('episodeNumber', episodeData.episodeNumber) .addProperty('seasonNumber', episodeData.seasonNumber) .addProperty('datePublished', episodeData.publishDate) .addProperty('duration', episodeData.duration) .addProperty('associatedMedia', { '@type': 'MediaObject', contentUrl: episodeData.audioUrl, encodingFormat: episodeData.audioFormat || 'audio/mpeg' }) .addProperty('partOfSeries', { '@type': 'PodcastSeries', name: episodeData.podcastName, url: episodeData.podcastUrl }) .addProperty('creator', episodeData.hosts?.map(host => ({ '@type': 'Person', name: host.name, url: host.url }))) .addProperty('keywords', episodeData.tags) .build(); }, // Software application software: (appData) => { return createSchema('SoftwareApp