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.

544 lines (514 loc) 18.2 kB
/** * Schema Templates - v1.12.0 * Pre-built schema templates with best practices * * Provides ready-to-use templates for common schema types with: * - Required fields pre-populated * - Optional fields as placeholders * - Best practice structures * - Easy customization */ export class SchemaTemplates { /** * Get all available template types * @returns {string[]} Array of template type names */ static getAvailableTemplates() { return [ 'Product', 'Article', 'BlogPosting', 'NewsArticle', 'LocalBusiness', 'Restaurant', 'Store', 'Event', 'Recipe', 'VideoObject', 'WebPage', 'Organization', 'Person', 'FAQPage', 'HowTo' ]; } /** * Get a template by type * @param {string} type - Schema type * @param {Object} data - Initial data to populate template * @returns {Object} Schema template */ static getTemplate(type, data = {}) { const templateMethod = `_create${type}Template`; if (typeof this[templateMethod] === 'function') { return this[templateMethod](data); } // Fallback to generic Thing template return this._createThingTemplate(type, data); } /** * Create Product template * @private */ static _createProductTemplate(data = {}) { return { '@context': 'https://schema.org', '@type': 'Product', name: data.name || '[Product Name]', description: data.description || '[Product Description]', image: data.image || data.images || [], brand: data.brand ? { '@type': 'Brand', name: data.brand } : undefined, offers: { '@type': 'Offer', url: data.url || data.offers?.url, priceCurrency: data.priceCurrency || data.offers?.priceCurrency || 'USD', price: data.price || data.offers?.price || '0.00', availability: data.availability || data.offers?.availability || 'https://schema.org/InStock', priceValidUntil: data.priceValidUntil || data.offers?.priceValidUntil, itemCondition: data.itemCondition || data.offers?.itemCondition || 'https://schema.org/NewCondition' }, aggregateRating: data.rating || data.aggregateRating ? { '@type': 'AggregateRating', ratingValue: data.rating?.ratingValue || data.aggregateRating?.ratingValue || '0', reviewCount: data.rating?.reviewCount || data.aggregateRating?.reviewCount || '0' } : undefined, review: data.reviews || data.review, sku: data.sku, mpn: data.mpn, gtin: data.gtin, gtin13: data.gtin13, gtin8: data.gtin8 }; } /** * Create Article template * @private */ static _createArticleTemplate(data = {}) { const now = new Date().toISOString(); return { '@context': 'https://schema.org', '@type': 'Article', headline: data.headline || data.name || '[Article Headline]', description: data.description || '[Article Description]', image: data.image || data.images || [], author: data.author ? { '@type': 'Person', name: typeof data.author === 'string' ? data.author : data.author.name } : undefined, publisher: data.publisher ? { '@type': 'Organization', name: typeof data.publisher === 'string' ? data.publisher : data.publisher.name, logo: data.publisher.logo ? { '@type': 'ImageObject', url: data.publisher.logo } : undefined } : undefined, datePublished: data.datePublished || now, dateModified: data.dateModified || now, mainEntityOfPage: { '@type': 'WebPage', '@id': data.url || data.mainEntityOfPage }, articleBody: data.articleBody || data.text, wordCount: data.wordCount, keywords: data.keywords, articleSection: data.articleSection || data.section }; } /** * Create BlogPosting template (extends Article) * @private */ static _createBlogPostingTemplate(data = {}) { const article = this._createArticleTemplate(data); return { ...article, '@type': 'BlogPosting', blogPost: data.blogPost }; } /** * Create NewsArticle template (extends Article) * @private */ static _createNewsArticleTemplate(data = {}) { const article = this._createArticleTemplate(data); return { ...article, '@type': 'NewsArticle', dateline: data.dateline, printColumn: data.printColumn, printEdition: data.printEdition, printPage: data.printPage, printSection: data.printSection }; } /** * Create LocalBusiness template * @private */ static _createLocalBusinessTemplate(data = {}) { return { '@context': 'https://schema.org', '@type': 'LocalBusiness', name: data.name || '[Business Name]', description: data.description, image: data.image || data.images || [], address: data.address ? { '@type': 'PostalAddress', streetAddress: data.address.streetAddress || data.address.street, addressLocality: data.address.addressLocality || data.address.city, addressRegion: data.address.addressRegion || data.address.state, postalCode: data.address.postalCode || data.address.zip, addressCountry: data.address.addressCountry || data.address.country || 'US' } : undefined, geo: data.geo || (data.latitude && data.longitude) ? { '@type': 'GeoCoordinates', latitude: data.latitude || data.geo?.latitude, longitude: data.longitude || data.geo?.longitude } : undefined, telephone: data.telephone || data.phone, url: data.url || data.website, priceRange: data.priceRange, openingHoursSpecification: data.openingHours || data.openingHoursSpecification, aggregateRating: data.rating || data.aggregateRating ? { '@type': 'AggregateRating', ratingValue: data.rating?.ratingValue || data.aggregateRating?.ratingValue || '0', reviewCount: data.rating?.reviewCount || data.aggregateRating?.reviewCount || '0' } : undefined, servesCuisine: data.servesCuisine, acceptsReservations: data.acceptsReservations }; } /** * Create Restaurant template (extends LocalBusiness) * @private */ static _createRestaurantTemplate(data = {}) { const business = this._createLocalBusinessTemplate(data); return { ...business, '@type': 'Restaurant', servesCuisine: data.servesCuisine || data.cuisine, acceptsReservations: data.acceptsReservations !== false, menu: data.menu }; } /** * Create Store template (extends LocalBusiness) * @private */ static _createStoreTemplate(data = {}) { const business = this._createLocalBusinessTemplate(data); return { ...business, '@type': 'Store', paymentAccepted: data.paymentAccepted, currenciesAccepted: data.currenciesAccepted }; } /** * Create Event template * @private */ static _createEventTemplate(data = {}) { return { '@context': 'https://schema.org', '@type': 'Event', name: data.name || '[Event Name]', description: data.description, image: data.image || data.images || [], startDate: data.startDate || new Date().toISOString(), endDate: data.endDate, eventStatus: data.eventStatus || 'https://schema.org/EventScheduled', eventAttendanceMode: data.eventAttendanceMode || 'https://schema.org/OfflineEventAttendanceMode', location: data.location ? { '@type': data.location.isVirtual ? 'VirtualLocation' : 'Place', name: data.location.name, address: data.location.address ? { '@type': 'PostalAddress', streetAddress: data.location.address.streetAddress || data.location.address.street, addressLocality: data.location.address.addressLocality || data.location.address.city, addressRegion: data.location.address.addressRegion || data.location.address.state, postalCode: data.location.address.postalCode || data.location.address.zip, addressCountry: data.location.address.addressCountry || data.location.address.country } : undefined, url: data.location.url } : undefined, organizer: data.organizer ? { '@type': 'Organization', name: typeof data.organizer === 'string' ? data.organizer : data.organizer.name, url: data.organizer.url } : undefined, performer: data.performer ? { '@type': 'Person', name: typeof data.performer === 'string' ? data.performer : data.performer.name } : undefined, offers: data.offers || data.price ? { '@type': 'Offer', url: data.offers?.url || data.url, price: data.offers?.price || data.price, priceCurrency: data.offers?.priceCurrency || data.priceCurrency || 'USD', availability: data.offers?.availability || 'https://schema.org/InStock', validFrom: data.offers?.validFrom } : undefined }; } /** * Create Recipe template * @private */ static _createRecipeTemplate(data = {}) { return { '@context': 'https://schema.org', '@type': 'Recipe', name: data.name || '[Recipe Name]', description: data.description, image: data.image || data.images || [], author: data.author ? { '@type': 'Person', name: typeof data.author === 'string' ? data.author : data.author.name } : undefined, datePublished: data.datePublished, prepTime: data.prepTime, cookTime: data.cookTime, totalTime: data.totalTime, recipeYield: data.recipeYield || data.servings, recipeCategory: data.recipeCategory || data.category, recipeCuisine: data.recipeCuisine || data.cuisine, recipeIngredient: data.recipeIngredient || data.ingredients || [], recipeInstructions: data.recipeInstructions || data.instructions || [], nutrition: data.nutrition ? { '@type': 'NutritionInformation', calories: data.nutrition.calories, fatContent: data.nutrition.fatContent, carbohydrateContent: data.nutrition.carbohydrateContent, proteinContent: data.nutrition.proteinContent } : undefined, aggregateRating: data.rating || data.aggregateRating ? { '@type': 'AggregateRating', ratingValue: data.rating?.ratingValue || data.aggregateRating?.ratingValue || '0', reviewCount: data.rating?.reviewCount || data.aggregateRating?.reviewCount || '0' } : undefined, keywords: data.keywords }; } /** * Create VideoObject template * @private */ static _createVideoObjectTemplate(data = {}) { return { '@context': 'https://schema.org', '@type': 'VideoObject', name: data.name || '[Video Title]', description: data.description, thumbnailUrl: data.thumbnailUrl || data.thumbnail || data.image, uploadDate: data.uploadDate || new Date().toISOString(), duration: data.duration, contentUrl: data.contentUrl || data.url, embedUrl: data.embedUrl, interactionStatistic: data.views ? { '@type': 'InteractionCounter', interactionType: 'https://schema.org/WatchAction', userInteractionCount: data.views } : undefined, publisher: data.publisher ? { '@type': 'Organization', name: typeof data.publisher === 'string' ? data.publisher : data.publisher.name, logo: data.publisher.logo ? { '@type': 'ImageObject', url: data.publisher.logo } : undefined } : undefined }; } /** * Create WebPage template * @private */ static _createWebPageTemplate(data = {}) { return { '@context': 'https://schema.org', '@type': 'WebPage', name: data.name || data.title || '[Page Title]', description: data.description, url: data.url, inLanguage: data.inLanguage || 'en', isPartOf: data.isPartOf, primaryImageOfPage: data.primaryImageOfPage || data.image ? { '@type': 'ImageObject', url: data.primaryImageOfPage || data.image } : undefined, datePublished: data.datePublished, dateModified: data.dateModified, author: data.author ? { '@type': 'Person', name: typeof data.author === 'string' ? data.author : data.author.name } : undefined }; } /** * Create Organization template * @private */ static _createOrganizationTemplate(data = {}) { return { '@context': 'https://schema.org', '@type': 'Organization', name: data.name || '[Organization Name]', description: data.description, url: data.url || data.website, logo: data.logo ? { '@type': 'ImageObject', url: data.logo } : undefined, contactPoint: data.contactPoint || data.phone || data.email ? { '@type': 'ContactPoint', telephone: data.contactPoint?.telephone || data.phone, email: data.contactPoint?.email || data.email, contactType: data.contactPoint?.contactType || 'customer service' } : undefined, address: data.address ? { '@type': 'PostalAddress', streetAddress: data.address.streetAddress || data.address.street, addressLocality: data.address.addressLocality || data.address.city, addressRegion: data.address.addressRegion || data.address.state, postalCode: data.address.postalCode || data.address.zip, addressCountry: data.address.addressCountry || data.address.country } : undefined, sameAs: data.sameAs || data.socialMedia }; } /** * Create Person template * @private */ static _createPersonTemplate(data = {}) { return { '@context': 'https://schema.org', '@type': 'Person', name: data.name || '[Person Name]', givenName: data.givenName || data.firstName, familyName: data.familyName || data.lastName, email: data.email, telephone: data.telephone || data.phone, url: data.url || data.website, image: data.image || data.photo, jobTitle: data.jobTitle, worksFor: data.worksFor ? { '@type': 'Organization', name: typeof data.worksFor === 'string' ? data.worksFor : data.worksFor.name } : undefined, sameAs: data.sameAs || data.socialMedia, address: data.address ? { '@type': 'PostalAddress', streetAddress: data.address.streetAddress || data.address.street, addressLocality: data.address.addressLocality || data.address.city, addressRegion: data.address.addressRegion || data.address.state, postalCode: data.address.postalCode || data.address.zip, addressCountry: data.address.addressCountry || data.address.country } : undefined }; } /** * Create FAQPage template * @private */ static _createFAQPageTemplate(data = {}) { const questions = data.questions || data.faqs || []; return { '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: questions.map(q => ({ '@type': 'Question', name: q.question || q.name, acceptedAnswer: { '@type': 'Answer', text: q.answer || q.text } })) }; } /** * Create HowTo template * @private */ static _createHowToTemplate(data = {}) { const steps = data.steps || data.instructions || []; return { '@context': 'https://schema.org', '@type': 'HowTo', name: data.name || '[How-To Title]', description: data.description, image: data.image || data.images || [], totalTime: data.totalTime, estimatedCost: data.estimatedCost, tool: data.tool || data.tools, supply: data.supply || data.supplies || data.materials, step: steps.map((step, index) => ({ '@type': 'HowToStep', position: index + 1, name: step.name || step.title || `Step ${index + 1}`, text: step.text || step.description, image: step.image, url: step.url })) }; } /** * Create generic Thing template (fallback) * @private */ static _createThingTemplate(type, data = {}) { return { '@context': 'https://schema.org', '@type': type || 'Thing', name: data.name || `[${type} Name]`, description: data.description, url: data.url, image: data.image || data.images || [] }; } /** * Clean template by removing undefined/null values * @param {Object} template - Template to clean * @returns {Object} Cleaned template */ static cleanTemplate(template) { const cleaned = {}; for (const [key, value] of Object.entries(template)) { if (value === undefined || value === null) { continue; } if (Array.isArray(value)) { if (value.length > 0) { cleaned[key] = value; } } else if (typeof value === 'object' && value !== null) { const cleanedObj = this.cleanTemplate(value); if (Object.keys(cleanedObj).length > 0) { cleaned[key] = cleanedObj; } } else { cleaned[key] = value; } } return cleaned; } /** * Merge template with custom data * @param {string} type - Schema type * @param {Object} data - Data to merge * @returns {Object} Merged schema */ static create(type, data = {}) { const template = this.getTemplate(type, data); const cleaned = this.cleanTemplate(template); return cleaned; } }